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 thestats:readscope. - Tenant-scoped: your organization is derived from the key. Audit events are included only when they target your organization.
- Errors use the standard envelope.
GET /gw/evidence
Section titled “GET /gw/evidence”| Param | Default | Values |
|---|---|---|
since | 30d | window start, as an offset back from now: 30d, 24h, … (d suffix for days, otherwise Go duration) |
until | now | window end, same format (until=1d = up to 24h ago); must be later than since |
limit | max_export_rows (100000) | global cap on exported records (usage + audit combined); 1..max_export_rows (out of range → 400) |
curl -H "Authorization: Bearer $GW_KEY" \ "https://api.sociaro.com/gw/evidence?since=185d" -o evidence.jsonlThe 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:
-
regionis the per-request attestation of where the request was physically executed (eu,us,global);nullmeans the record predates region tracking. -
auditlines are operator actions that targeted your organization. The export is tenant-scoped at the source: the underlyingoperator_audit_logis cross-tenant, but the query returns only rows whosetargetequals your organization id, so you never see actions on anyone else. Each line carries the actingoperator_email, theaction, and adetailobject (the action’s JSON payload, ornull). -
The
actionfield 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); detailcarrieslimit_usd,periodcredit_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: truein the checksum means the window holds more records thanlimit— the chronologically earliestlimitrecords were exported; narrow the window and export in chunks.
Integrity: the hash chain
Section titled “Integrity: the hash chain”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:
python3 scripts/verify_evidence.py evidence.jsonl# OK: 2 records, chain validThe 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.
Retention policy
Section titled “Retention policy”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.
- Open-source code default: the sweep ships disabled
(
- 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.
Field mapping (EU AI Act / ISO/IEC 42001)
Section titled “Field mapping (EU AI Act / ISO/IEC 42001)”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 field | EU AI Act reference | ISO/IEC 42001 control theme |
|---|---|---|
usage.created_at, retention ≥ 185d | Art. 26(6) — deployers keep automatically generated logs ≥ 6 months | A.6.2.6 — AI system operation and monitoring (event logging) |
usage.provider, usage.model | Art. 26 — deployer oversight of which AI system processed the request | A.6.2.6 / A.4 — resource and asset documentation |
usage.region | data-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_code | Art. 26 — incident visibility for deployer oversight | A.8.4 — incident-relevant event records |
audit.* (operator actions) | Art. 26 — human oversight of system configuration | A.9.4 / A.6.2.6 — accountability for administrative changes |
| hash chain + deletion journal | record integrity expected of Art. 26(6) logs | A.6.2.6 — protection of log integrity |
Article 50 helper: X-AI-Generated header
Section titled “Article 50 helper: X-AI-Generated header”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.