Database & migrations¶
api-test uses PostgreSQL 14+ for the audit log and (when enabled) the
bcrypt-backed API key store. Migrations live in
pkg/database/migrate/migrations
and are embedded into the binary via go:embed. They run on every
startup via golang-migrate;
already-applied migrations are skipped.
The database is optional when both audit.enabled and
api_keys.db.enabled are false; api-test runs without a DB in
anonymous + file-only-keys mode (the M1 happy path).
Configuration¶
database:
url: "${APITEST_DB_URL:-postgres://api:api@localhost:5432/apitest?sslmode=disable}"
max_open_conns: 25
max_idle_conns: 5
conn_max_lifetime: 1h
The DSN passes through to pgxpool.ParseConfig, so any
pgx-supported form
works: postgres://, postgresql://, or libpq key=value strings.
Schema¶
Three tables:
api_keys— bcrypt-hashed API keys (whenapi_keys.db.enabled). Columns:id,name,hash,description,created_by,created_at,expires_at,last_used_at. Unique onname.audit_events— indexable summary of one inbound request. Columns:id,ts,duration_ms,request_id,session_id,user_subject,user_email,auth_type,api_key_name,method,path,route_name,endpoint_group,status,bytes_in,bytes_out,success,error_message,error_category,remote_addr,user_agent. Indexed on(ts DESC),(route_name, ts DESC),(path, ts DESC),(user_subject, ts DESC),(session_id, ts DESC),(status, ts DESC).audit_payloads— full request/response envelope joined 1:1 withaudit_events.id. Columns:event_id,request_headers,request_query,request_content_type,request_body,request_size_bytes,request_truncated,request_remote_addr,response_headers,response_content_type,response_body,response_size_bytes,response_truncated,replayed_from,captured_at. JSONB GIN indexes onrequest_headersandresponse_headersfor portal filtering.
The two-table layout keeps the summary row free of multi-KB JSONB so time/route/identity queries stay fast; the payload join only runs when an operator drills into a single event in the portal.
ON DELETE CASCADE from audit_payloads.event_id to audit_events.id
keeps retention cleanup atomic.
Retention¶
Default is 7 days (lower than mcp-test's 30 — api-test responses can be much larger; export endpoints emit 100 MiB bodies that inflate the payload table fast). A future migration adds a periodic cleanup job; for now, prune manually:
Body capture caps¶
Bodies that exceed audit.max_payload_bytes (default 1 MiB) are
truncated and the matching request_truncated / response_truncated
flag is set. The captured prefix is what's stored. Operators in
privacy-sensitive deployments can set:
audit:
capture_payloads: false # only the audit_events summary row
# or
capture_payloads: true
capture_headers: false # bodies but no headers
Operations¶
- Pre-flight smoke before pointing at production:
- Migration history lives in
schema_migrations(golang-migrate default). - Down migrations ship for every up; rollback works via
migrate -path … -database … down 1. The binary itself only runsup. - Connection pool tuning: defaults (25 open, 5 idle, 1h lifetime)
are conservative. For high-throughput deployments, raise
max_open_connsand lowerconn_max_lifetimeto rotate connections faster.