Skip to content

Evidence export & retention (`/gw/evidence`)

The gateway keeps an auditable record of every proxied request (metadata only — never request or response bodies) and of every operator action on your organization. GET /gw/evidence exports that record as a tamper-evident NDJSON file you can hand to an auditor or attach to a procurement questionnaire, and a configurable retention policy governs how long the underlying rows are kept.

  • Auth: Authorization: Bearer gw_live_… with the stats:read scope.
  • Tenant-scoped: your organization is derived from the key. Audit events are included only when they target your organization.
  • Errors use the standard envelope.
ParamDefaultValues
since30dwindow start, as an offset back from now: 30d, 24h, … (d suffix for days, otherwise Go duration)
untilnowwindow end, same format (until=1d = up to 24h ago); must be later than since
limitmax_export_rows (100000)global cap on exported records (usage + audit combined); 1..max_export_rows (out of range → 400)
Terminal window
curl -H "Authorization: Bearer $GW_KEY" \
"https://api.sociaro.com/gw/evidence?since=185d" -o evidence.jsonl

The response is application/x-ndjson (Content-Disposition: attachment; filename="evidence-<client_id>-<date>.jsonl"): one JSON object per line, in chronological order, framed by a header and a checksum line:

{"type":"header","format":"gw-evidence/v1","client_id":"<uuid>","since":"2026-05-01T00:00:00Z","until":"2026-06-11T00:00:00Z","generated_at":"2026-06-11T09:00:00Z","prev":""}
{"type":"usage","id":"<uuid>","created_at":"2026-05-02T10:15:00Z","provider":"openai","model":"gpt-4o","region":"us","cost_usd":0.00075,"status_code":200,"total_tokens":150,"latency_ms":840,"prev":"<sha256 of line 1>"}
{"type":"audit","id":"<uuid>","created_at":"2026-05-03T08:00:00Z","action":"set_budget","operator_email":"ops@sociaro.com","detail":{"limit_usd":"100","period":"month"},"prev":"<sha256 of line 2>"}
{"type":"checksum","records":2,"truncated":false,"prev":"<sha256 of line 3>"}

Notes:

  • region is the per-request attestation of where the request was physically executed (eu, us, global); null means the record predates region tracking.

  • audit lines are operator actions that targeted your organization. The export is tenant-scoped at the source: the underlying operator_audit_log is cross-tenant, but the query returns only rows whose target equals your organization id, so you never see actions on anyone else. Each line carries the acting operator_email, the action, and a detail object (the action’s JSON payload, or null).

  • The action field is a stable verb. The values an organization can see in its own export include:

    actionOperator did this to your org
    provision_clientCreated your organization
    set_budgetSet or changed a budget (org, sub-account or key level); detail carries limit_usd, period
    credit_balanceAdjusted your prepaid balance (top-up / correction)
    set_billingChanged your billing mode / settings
    set_routing_policyChanged your region routing policy (any / prefer_eu / eu_only)
    upsert_entitlement / delete_entitlementChanged which models a key or your organization may call
    create_keyProvisioned an API key for your organization
    revoke_keyRevoked one of your API keys
    set_credential / delete_credentialSet or removed a provider credential your traffic uses

    This is the set you are likely to encounter; the operator console is the source of these verbs, so treat unfamiliar values as new operator actions rather than errors, and key your tooling off the fields you need (action, operator_email, created_at, detail) rather than a frozen enum.

  • Request/response bodies and prompts are never included in the export.

  • truncated: true in the checksum means the window holds more records than limit — the chronologically earliest limit records were exported; narrow the window and export in chunks.

Every line carries a prev field: the lowercase hex SHA-256 of the exact bytes of the previous line (without the trailing newline). The header has prev: ""; the checksum line closes the chain and states the record count. Changing, inserting, reordering or deleting any line breaks verification of every line after it.

Verification needs no JSON canonicalization — only raw line reading and SHA-256:

Terminal window
python3 scripts/verify_evidence.py evidence.jsonl
# OK: 2 records, chain valid

The script (Python 3, stdlib only) is in the gateway repository at scripts/verify_evidence.py; it exits non-zero with the first mismatching line number on tampering. Any independent implementation following the rule above will reach the same verdict.

Operators configure retention for usage and audit records (retention section in the gateway config):

  • Retention is 185 days (≥ 6 months) when the sweep is enabled, aligned with the EU AI Act Art. 26(6) log-keeping expectation for deployers. There are two distinct postures to keep straight:
    • Open-source code default: the sweep ships disabled (retention.enabled: false). With it off, nothing is ever deleted — a self-hosted gateway retains indefinitely until an operator turns retention on.
    • Sociaro EU production: retention is enabled at 185 days with a 1-hour sweep interval. This is the shipped posture behind the EU production gateway; see EU compliance.
  • Deletion runs in batches in the background. Every sweep that removes rows writes a row to a deletion journal (retention_journal: table, row count, cutoff, configured retention days). The journal itself is never swept — it is the durable proof that data was erased under the policy.
  • Usage rows with a not-yet-exported billing event are skipped until the event is sent, so retention can never erase unbilled usage.
  • The async job ledger (/gw/async) is a financial record and is not subject to this sweep.

The export is designed so each field is useful as supporting evidence. The gateway is not a certification, and this table is not legal advice — the mapping shows which obligations and control themes each field is aligned with; your own compliance assessment remains yours.

Exported fieldEU AI Act referenceISO/IEC 42001 control theme
usage.created_at, retention ≥ 185dArt. 26(6) — deployers keep automatically generated logs ≥ 6 monthsA.6.2.6 — AI system operation and monitoring (event logging)
usage.provider, usage.modelArt. 26 — deployer oversight of which AI system processed the requestA.6.2.6 / A.4 — resource and asset documentation
usage.regiondata-residency commitments referenced in DPAs/SCCs (supports GDPR Ch. V posture)A.7.5 — data provenance and location records
usage.cost_usd, usage.total_tokens, usage.latency_ms— (operational accountability)A.6.2.6 — monitoring and measurement records
usage.status_codeArt. 26 — incident visibility for deployer oversightA.8.4 — incident-relevant event records
audit.* (operator actions)Art. 26 — human oversight of system configurationA.9.4 / A.6.2.6 — accountability for administrative changes
hash chain + deletion journalrecord integrity expected of Art. 26(6) logsA.6.2.6 — protection of log integrity

EU AI Act Art. 50 places transparency (disclosure) obligations on providers and deployers of generative systems: people must be able to tell they are interacting with AI, and certain generated content must be disclosed as such.

The gateway can help propagate that signal: operators may enable, per LLM provider, an X-AI-Generated: true response header on successful (2xx) responses proxied from that provider (providers[].article50_header in the gateway config). Error responses never carry the header — there is no generated content in them.

This header is plumbing, not compliance: it gives your downstream services a machine-readable marker to build user-facing disclosure on. Your Art. 50 disclosure obligations remain your own — enabling the header does not discharge them.