Configuration
TOML config file with environment variable overrides.
Config file
Keplor looks for keplor.toml by default. Override with --config:
$ keplor run --config /etc/keplor/keplor.toml Full example
# keplor.toml
[server]
listen_addr = "0.0.0.0:8080"
shutdown_timeout_secs = 25
request_timeout_secs = 30
max_connections = 10000
[storage]
db_path = "/var/lib/keplor/keplor.db"
retention_days = 90 # legacy global GC (prefer [retention] tiers)
wal_checkpoint_secs = 300
max_db_size_mb = 0 # 0 = unlimited
read_pool_size = 4 # SQLite read connections (1-64)
gc_interval_secs = 3600 # how often GC runs (0 = disabled)
[auth]
api_keys = ["prod-svc:sk-prod-abc123", "staging:sk-staging-def456"]
# Extended format with tier assignment:
# [[auth.api_key_entries]]
# id = "pro-user"
# secret = "sk-pro-key"
# tier = "pro"
[retention]
default_tier = "free"
[[retention.tiers]]
name = "free"
days = 7
[[retention.tiers]]
name = "pro"
days = 90
# [[retention.tiers]]
# name = "team"
# days = 180
[cors]
allowed_origins = [] # empty = same-origin only; ["*"] = allow all
[pipeline]
batch_size = 64
max_body_bytes = 10485760
channel_capacity = 32768
[idempotency]
enabled = true
ttl_secs = 300
max_entries = 100000
[rate_limit]
enabled = false
requests_per_second = 100.0
burst = 200
# Optional: archive old events to S3/R2 (requires --features s3)
# [archive]
# bucket = "keplor-archive"
# endpoint = "https://<account-id>.r2.cloudflarestorage.com"
# region = "auto"
# access_key_id = "..."
# secret_access_key = "..."
# prefix = "events"
# archive_after_days = 30
# archive_threshold_mb = 500
# archive_batch_size = 10000
# [tls]
# cert_path = "/etc/keplor/cert.pem"
# key_path = "/etc/keplor/key.pem" [server]
| Key | Type | Default | Description |
|---|---|---|---|
listen_addr | string | "0.0.0.0:8080" | Bind address |
shutdown_timeout_secs | u64 | 25 | Graceful shutdown timeout (drain + checkpoint) |
request_timeout_secs | u64 | 30 | Per-request timeout |
max_connections | usize | 10000 | Maximum concurrent connections |
[storage]
| Key | Type | Default | Description |
|---|---|---|---|
db_path | string | "keplor.db" | SQLite database path |
retention_days | u64 | 90 | Legacy global GC threshold (0 = disabled). Prefer [retention] tiers. |
wal_checkpoint_secs | u64 | 300 | WAL truncation interval (0 = disabled) |
max_db_size_mb | u64 | 0 | Max database size in MB (0 = unlimited). Returns HTTP 507 when exceeded. |
read_pool_size | usize | 4 | Number of SQLite read connections (range: 1–64) |
gc_interval_secs | u64 | 3600 | How often GC runs in seconds (0 = disabled) |
[auth]
| Key | Type | Default | Description |
|---|---|---|---|
api_keys | string[] | [] | Simple API keys (id:secret or bare secret). Assigned to default_tier. Empty = open access. |
api_key_entries | object[] | [] | Extended keys with explicit tier: { id, secret, tier } |
[retention]
Per-tier retention policies. GC runs one pass per tier, deleting events older than the tier's days threshold.
| Key | Type | Default | Description |
|---|---|---|---|
default_tier | string | "free" | Tier assigned to simple-format keys and unauthenticated requests |
tiers | object[] | free (7d), pro (90d) | Named tiers with retention days. days = 0 keeps events forever. |
Tiers are just names — add "team", "enterprise", or any custom tier via config. No code changes needed.
[archive] (optional, --features s3)
Archive old events to an S3-compatible object store (Cloudflare R2, MinIO, AWS S3) as compressed JSONL files. Archived events are deleted from SQLite; daily rollups are preserved.
| Key | Type | Default | Description |
|---|---|---|---|
bucket | string | S3 bucket name | |
endpoint | string | S3 endpoint URL | |
region | string | Region ("auto" for R2, "us-east-1" for AWS) | |
access_key_id | string | Access key | |
secret_access_key | string | Secret key | |
prefix | string | "" | Key prefix in bucket (e.g. "events") |
path_style | bool | false | Path-style addressing (required for MinIO) |
archive_after_days | u64 | 30 | Archive events older than this many days |
archive_after_hours | u64 | 0 | Sub-day archival (hours). Overrides archive_after_days when non-zero. Set to 1 for hourly offload. |
archive_threshold_mb | u64 | 0 | Also archive when SQLite exceeds this size (MB). 0 = age-only. |
archive_batch_size | usize | 10000 | Maximum events per JSONL archive file |
archive_interval_secs | u64 | 3600 | How often the archive loop runs (seconds). Default: 1 hour. |
Archival runs every archive_interval_secs (default 1 hour). Events are grouped by (user_id, day), serialized to JSONL, compressed with zstd, and uploaded to S3/R2. S3 connectivity is verified at startup. See Event Archival for the full lifecycle.
Important: Set archive_after_days lower than your shortest retention tier's days value, or GC will delete events before they can be archived.
[cors]
| Key | Type | Default | Description |
|---|---|---|---|
allowed_origins | string[] | [] | CORS origin allowlist. Empty = same-origin only. ["*"] = allow all (not recommended). |
[pipeline]
| Key | Type | Default | Range | Description |
|---|---|---|---|---|
batch_size | usize | 64 | 1 – 100,000 | Events per batch flush |
max_body_bytes | usize | 10485760 | 1 – 100MB | Max request body size |
channel_capacity | usize | 32768 | 1 – ∞ | Batch writer queue depth. Raise for bursty traffic. |
[idempotency]
| Key | Type | Default | Description |
|---|---|---|---|
enabled | bool | true | Enable idempotency key support |
ttl_secs | u64 | 300 | Cache TTL for idempotency keys (seconds) |
max_entries | usize | 100000 | Maximum cached idempotency keys (LRU eviction) |
[rate_limit]
| Key | Type | Default | Description |
|---|---|---|---|
enabled | bool | false | Enable per-key rate limiting |
requests_per_second | f64 | 100.0 | Token bucket refill rate per API key |
burst | usize | 200 | Maximum burst size per API key |
[tls]
Optional. When present, the server listens with HTTPS via rustls.
| Key | Type | Description |
|---|---|---|
cert_path | string | Path to PEM-encoded certificate chain |
key_path | string | Path to PEM-encoded private key |
Environment variables
Override any key with KEPLOR_ prefix and underscore nesting:
$ KEPLOR_SERVER_LISTEN_ADDR=0.0.0.0:9090 \
KEPLOR_STORAGE_DB_PATH=/tmp/keplor.db \
KEPLOR_AUTH_API_KEYS=sk-key1,sk-key2 \
keplor run Environment variables take precedence over the config file.
SQLite pragmas
Applied automatically at startup:
| Pragma | Value | Purpose |
|---|---|---|
journal_mode | WAL | Concurrent reads during writes |
synchronous | NORMAL | Fsync on checkpoint only |
mmap_size | 256 MB | Memory-mapped I/O |
busy_timeout | 5000 ms | Retry on lock contention |
cache_size | 64 MB | Page cache |
temp_store | MEMORY | Temp tables in RAM |
wal_autocheckpoint | 1000 pages | Auto-checkpoint WAL to bound growth |
Validation
Config is validated at startup. Invalid values produce immediate errors:
Error: invalid config: pipeline.batch_size must be > 0