SIEM and evidence export

The audit trail, as a file your tooling already reads.

One GET returns every verification event AffixIO holds for your keys as newline-delimited JSON. Your SIEM ingests it without a transformation step, and your GRC team gets the same data as a single JSON document when an auditor asks for one file rather than a stream.

Last checked against the running implementation on . Written for security engineers and the people who answer audit requests.

Rows of rack-mounted servers with patch cabling, photographed at CERN
Evidence is only useful where it can be searched. Photograph by Hugovanmeijeren, CC BY-SA 3.0, cropped and desaturated.

The call

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

Parameters

since
ISO-8601 lower bound, inclusive. Omit it and the export starts from the earliest record held for your keys.
until
ISO-8601 upper bound, inclusive. Pair it with since to page through a long history.
format
ndjson by default, or json for a single document with a header block around the events array. Anything else returns 400.
limit
1 to 10000, defaulting to 10000. When more events match, the most recent ones are kept.

Response headers worth reading

NDJSON responses
HeaderMeaning
Content-Typeapplication/x-ndjson; charset=utf-8
Content-DispositionAn attachment filename stamped with the generation time, so scheduled pulls do not overwrite each other.
X-Affix-Export-CountNumber of lines in this response.
X-Affix-Export-Truncated1 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.

One line, explained

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"}}
tsISO-8601 timestamp in UTC. Lines are sorted ascending, which means an append to your index never arrives out of order within one response.
eventOne of eleven names. Use it as the primary dimension in a dashboard.
sourceWhich internal record produced the line: key-usage-log, decision-log, audit-merkle, spent-registry, managed-keys or webhooks.
key_idThe API key that triggered it, or null where the record has no key attached, such as a spend recorded at a gate.
detailFields specific to the source. Map the ones you care about at ingest and keep the rest as a raw object.

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.

Event catalogue

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 names, their source and the fields inside detail
EventSourceDetail fields
provekey-usage-log, decision-logmethod, path, status, or the decision fields when it came from a recorded outcome
verifykey-usage-log, decision-logdecision, circuit_id, proof_id, proof_digest, decision_source
gatekey-usage-log, decision-logAs above, for gate admission calls
attestkey-usage-log, decision-logAs above, for attestation calls
merkle.leafaudit-merkleaudit_event, digest, leaf_hash, circuit_id, proof_id
spendspent-registrydigest, circuit_id, proof_id, spent_source, gate_id
revokespent-registrySame fields, for a proof withdrawn rather than consumed
key.revokedmanaged-keyslabel, revoked_via, owner_user_id, owner_key_id
webhook.deliverywebhookswebhook_id, label, url, status, failure_count, active, events
decisiondecision-logA recorded outcome that did not map to one of the four call types
api.requestkey-usage-logmethod, 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.

Into a pipeline

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.

Scheduled pull with a watermark

#!/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.

Field mapping worth doing at ingest

Suggested mapping
Export fieldIndex as
tsThe event timestamp. Do not let the shipper substitute ingest time; a backfilled window would be plotted at the wrong moment.
eventKeyword. The primary dimension for dashboards and alerts.
key_idKeyword. This is how you attribute activity to an integration.
detail.decisionKeyword with values yes and no. A sudden shift in the ratio is the signal most teams alert on.
detail.digestKeyword. Joins the export to a Merkle inclusion proof and to any webhook you received.
detail.statusInteger. 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.

For an evidence pack

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.

Limits

  • Ten thousand events per request. Beyond that the most recent are returned and the truncation header is set. Page with since and until.
  • The export is a pull. There is no push channel and no streaming cursor. Latency is whatever your schedule is.
  • webhook.delivery lines describe the last delivery per endpoint, not a full delivery history. AffixIO does not keep one.
  • Timestamps come from the API host clock in UTC. If your correlation depends on sub-second alignment with your own logs, account for that.
  • Scope is credential-bound. A key that was revoked before your Hub account existed will not appear under that account.
  • The export holds no personal data because the verification path never receives any. Digests, identifiers, paths and hashes only.

Questions

Format, ceilings, scope and what the file does not prove.

What format does the export use?

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.

How many events can one request return?

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.

Which events appear?

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.

Does the export contain personal data?

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.

How do I schedule this into a SIEM?

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.

Is this an attestation of compliance?

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.

Next

Pair the export with signed pushes for latency, or check what the audit tree publishes without any credential at all.