GRC teams buy export, not a dashboard login
If the evidence cannot land in the platform you already run, the attestation does not matter in an audit week.
SIEM and evidence export
NDJSON for GRC and SIEM. Completeness when webhooks were not enough.
GET https://api.affix-io.com/api/export/siem with an API key or a Hub session. The response is application/x-ndjson, one event object per line, sorted oldest first. /api/export/evidence is the same endpoint under the name your compliance team is more likely to recognise.
curl -sS "https://api.affix-io.com/api/export/siem?since=2026-08-01T00:00:00Z&limit=5000" \
-H "Authorization: Bearer $AFFIX_API_KEY" \
-o affix-evidence.ndjson
# auditor variant: one document instead of a stream
curl -sS "https://api.affix-io.com/api/export/evidence?since=2026-07-01T00:00:00Z&format=json" \
-H "Authorization: Bearer $AFFIX_API_KEY" \
-o affix-evidence.json
since to page through a long history.ndjson by default, or json for a single document with a header block around the events array. Anything else returns 400.| Header | Meaning |
|---|---|
| Content-Type | application/x-ndjson; charset=utf-8 |
| Content-Disposition | An attachment filename stamped with the generation time, so scheduled pulls do not overwrite each other. |
| X-Affix-Export-Count | Number of lines in this response. |
| X-Affix-Export-Truncated | 1 when the window held more events than the limit allowed. Narrow the window and pull again. |
Without credentials the endpoint returns 401. Scope follows the credential: a Hub session covers the keys on that account, an API key on its own covers that key, and an administrator token covers everything.
Every line has the same five keys. Only detail varies, and it varies by the source that produced the event.
{"ts":"2026-08-14T19:39:39.740Z","event":"verify","source":"decision-log","key_id":"9f42c1a0b7e35d81","detail":{"decision":"yes","circuit_id":"yesno","proof_id":"prf_9c2d18a6","proof_digest":"aeec8191...","decision_source":"verify"}}
key-usage-log, decision-log, audit-merkle, spent-registry, managed-keys or webhooks.The format=json variant wraps the same lines in a header: generated_at, the since and until you asked for, count, truncated, the available formats and the events array. That header is what makes it usable as a standalone exhibit, because it states the window rather than leaving it to a filename.
Eleven event names across six sources. The first four are derived from the request path and the recorded decision, which is why a single verification can appear as both a verify line and a merkle.leaf line.
| Event | Source | Detail fields |
|---|---|---|
| prove | key-usage-log, decision-log | method, path, status, or the decision fields when it came from a recorded outcome |
| verify | key-usage-log, decision-log | decision, circuit_id, proof_id, proof_digest, decision_source |
| gate | key-usage-log, decision-log | As above, for gate admission calls |
| attest | key-usage-log, decision-log | As above, for attestation calls |
| merkle.leaf | audit-merkle | audit_event, digest, leaf_hash, circuit_id, proof_id |
| spend | spent-registry | digest, circuit_id, proof_id, spent_source, gate_id |
| revoke | spent-registry | Same fields, for a proof withdrawn rather than consumed |
| key.revoked | managed-keys | label, revoked_via, owner_user_id, owner_key_id |
| webhook.delivery | webhooks | webhook_id, label, url, status, failure_count, active, events |
| decision | decision-log | A recorded outcome that did not map to one of the four call types |
| api.request | key-usage-log | method, path, status for everything else your key called |
If you are building a first dashboard, three lines carry most of the value: verify with detail.decision as the split, spend for single-use enforcement, and key.revoked for access changes. The rest is there when an investigation needs it.
NDJSON is deliberately boring. One object per line is what Splunk, Elastic, OpenSearch, Loki and every file-tailing shipper already handle, so the integration is a scheduled pull and a spool directory rather than a connector.
#!/usr/bin/env bash
set -euo pipefail
SPOOL=/var/log/affix
STATE="$SPOOL/.watermark"
SINCE=$(cat "$STATE" 2>/dev/null || echo "2026-01-01T00:00:00Z")
NOW=$(date -u +%Y-%m-%dT%H:%M:%SZ)
curl -fsS "https://api.affix-io.com/api/export/siem?since=$SINCE&until=$NOW" \
-H "Authorization: Bearer $AFFIX_API_KEY" \
-o "$SPOOL/affix-$NOW.ndjson"
echo "$NOW" > "$STATE"
Point your existing shipper at /var/log/affix/*.ndjson and stop there. Keep the watermark on disk rather than deriving it from filenames, and overlap the window by a minute if your job runs on a host whose clock you do not control.
| Export field | Index as |
|---|---|
| ts | The event timestamp. Do not let the shipper substitute ingest time; a backfilled window would be plotted at the wrong moment. |
| event | Keyword. The primary dimension for dashboards and alerts. |
| key_id | Keyword. This is how you attribute activity to an integration. |
| detail.decision | Keyword with values yes and no. A sudden shift in the ratio is the signal most teams alert on. |
| detail.digest | Keyword. Joins the export to a Merkle inclusion proof and to any webhook you received. |
| detail.status | Integer. HTTP status for request-derived lines. |
Webhooks and the export answer different questions. A webhook tells you within seconds that something happened and can be dropped after three failed attempts. The export is the complete record for a window, pulled on your schedule. Teams that care about both latency and completeness run both, and reconcile on digest.
When somebody asks you to show that a control operated over a period, three things usually have to be in the same folder: the events, the window they cover, and something an outsider can check independently.
The events and the window come from format=json. The document states generated_at, since, until and count at the top, so the exhibit describes its own scope instead of relying on a covering note.
The independent check comes from the audit tree. Every merkle.leaf line carries a digest and a leaf_hash, and the current root is public at api.affix-io.com/v1/merkle/root. A reviewer can request an inclusion proof for any digest in your pack and confirm it sits under the published root without holding an account or seeing anything about the person the check concerned.
What the export is not: a certification, an opinion on your controls, or a statement about systems AffixIO cannot see. It records what the API observed for your keys. Everything else in the pack is yours to write. The compliance page sets out where the boundary sits, and the NIST alignment page is more specific still about which standards are implemented and which are not claimed.
since and until.webhook.delivery lines describe the last delivery per endpoint, not a full delivery history. AffixIO does not keep one.If the evidence cannot land in the platform you already run, the attestation does not matter in an audit week.
One object per line, stable field names.
Keep a file on disk and overlap the window. That engineering watermark is not a hidden signature.
Use pushes for latency.
WebhooksFormat, ceilings, scope and what the file does not prove.
Newline-delimited JSON by default, served as application/x-ndjson with one event object per line. Add format=json for a single document with generated_at, since, until, count, truncated and the events array.
Ten thousand. If more match the window, the most recent ten thousand are returned and X-Affix-Export-Truncated is set to 1. Narrow the window and page through it.
prove, verify, gate, attest, merkle.leaf, spend, revoke, key.revoked, webhook.delivery, decision and api.request, drawn from the key usage log, the decision log, the Merkle audit tree, the spend registry, managed keys and webhook endpoints.
No. Lines carry timestamps, event names, key identifiers, request paths, proof digests, circuit identifiers and Merkle hashes. The verification model never puts the underlying attribute on the wire.
Run a job that calls the endpoint with since set to the end of the previous run, write the NDJSON to a spool file and let your existing shipper pick it up. No transformation step is needed, because NDJSON is what those pipelines already ingest.
No. It is a record of what the AffixIO API observed for your keys. It supports an audit, it does not certify your controls, and AffixIO is not a compliance certification.
Every verification event AffixIO holds for your keys, as newline-delimited JSON.
So the next pull does not miss or fully duplicate a window. That engineering watermark is a pull state, not a hidden signature.
They need the file in the platform they already run. That is this page.
Pair the export with signed pushes for latency, or check what the audit tree publishes without any credential at all.