API reference
Introspection API
audit.read scope and every response carries X-Audit-Seq, the workspace's current high-water seq.Routes
| Route | Returns |
|---|---|
GET /v1/audit-events/cursor | {seq}: the latest seq (0 before the first event). Your mark. |
GET /v1/audit-events | A page of events, filtered, newest first by default. |
GET /v1/audit-events/:id | One event by aud_… id (404 for another workspace's id). |
GET /v1/audit-events/wait | Answers as soon as matching events exist, or at a timeout. |
GET /v1/audit-events/export | A CSV or NDJSON file of matching events. |
GET /v1/audit-events/actions | The action catalogue (the table on Audit events). |
Filters
The list, wait and export routes take the same filters; all are combined with AND.
| Parameter | Matches |
|---|---|
after_seq | Events with seq greater than this: everything since your mark. |
before_seq | Events with seq less than this. |
since, until | ISO 8601 instants (since inclusive, until exclusive). |
action | Comma list, up to 20 entries, ORed. An entry ending in .* matches a family: dispute.* matches dispute.accept but not dispute_x.accept. |
correlation_id | Exact, or a prefix: at least 4 characters and one trailing * (ci-4812:*). _ and % are literal characters. * alone or a shorter prefix (ci*) is rejected with 400 validation_failed, whose details name the field correlation_id and the constraint invalid_correlation_filter. |
request_id | Exact req_…. |
target_type, target_id | Exact, e.g. dispute and dsp_…. |
actor_type, actor_id | E.g. api_key and tak_…. |
outcome | success, denied or failed. |
source | portal, api or system. |
parent_id | The children of one batch or lifecycle run (aud_…). |
List and get
curl -s "https://api.dispute.markets/v1/audit-events?correlation_id=ci-4812:*&order=asc&limit=200" \
-H "Authorization: Bearer $DM_API_KEY"The response is {data, pagination: {next_cursor, has_more}, high_water_seq}. limit is 1 to 200 (default 50). Pass next_cursor back as cursor with the same filters and order; a cursor reused with other filters is 400 invalid_cursor.
high_water_seq is read in the same snapshot as data: continuing with after_seq=high_water_seq neither misses nor repeats an event.
Mark a point
GET /v1/audit-events/cursor returns the workspace's current seq. Call it before acting and filter with after_seq afterwards, so you only ever look at what happened since. Sequence numbers follow commit order, so a slow transaction that commits after your mark is never skipped.
Wait instead of sleeping
GET /v1/audit-events/wait is a bounded long-poll: it answers as soon as at least min_count matching events exist after after_seq, or when timeout_s runs out. Use it for anything that happens after a response (generate jobs, lifecycle runs, webhook deliveries), and to avoid a sleep-then-check loop.
curl -s "https://api.dispute.markets/v1/audit-events/wait?after_seq=1041&action=generate_job.run&target_id=gj_…&timeout_s=20" \
-H "Authorization: Bearer $DM_API_KEY"
{ "satisfied": true, "data": [ { "action": "generate_job.run", … } ], "high_water_seq": 1047 }Rules
after_seqis required, plus at least one narrowing filter:correlation_id,request_id,target_id, oractionmade only of exact names (dispute.accept,dispute.challenge) or prefixes of at least two segments (webhook_delivery.send.*). A family such asdispute.*alone would tail the whole workspace and is400 wait_filter_too_broad.min_count1 to 50 (default 1);timeout_s1 to 25 (default 10). Longer waits wait again.- Always 200. On a timeout
satisfiedisfalseanddataholds the partial matches seen so far, so a failing test can print what did happen.dataholds at most 200 events. - To keep waiting without gaps or repeats, call again with
after_seq=high_water_seq. - At most 12 concurrent waits per caller and 12 per workspace on each API server; over that,
429 too_many_waitswithRetry-After: 1. Each server's wait slots are shared fairly between workspaces, so another workspace cannot use them all up: when they are full, a busy workspace's oldest wait may answer early withsatisfied: false, just like a timeout. Events committed on another server are seen within about a second.
The example fixture wraps it like this:
async waitFor(filter: AuditFilter, options: WaitOptions = {}): Promise<AuditEvent[]> {
const minCount = options.minCount ?? 1;
const deadline = Date.now() + (options.timeout ?? 15_000);
for (;;) {
const res = await this.waitOnce(filter, minCount, deadline - Date.now());
if (res.satisfied) return res.data;
if (Date.now() >= deadline) {
throw new Error(
`waitFor(${JSON.stringify(filter)}) saw ${res.data.length} of ${minCount} event(s):\n` +
res.data.map((e) => ` ${describeEvent(e)}`).join('\n'),
);
}
}
}Export
curl -s -o trail.csv "https://api.dispute.markets/v1/audit-events/export?format=csv&since=2026-10-01T00:00:00Z" \
-H "Authorization: Bearer $DM_API_KEY"format=csv(one header row;changesandmetadataas JSON cells) orformat=ndjson(one event per line). Same filters as the list, oldest first.- At most 31 days per export (default: the last 31 days; wider is
400 export_range_too_large). - At most 100,000 rows for people and 10,000 for API keys, checked before the first byte (
413 export_too_large: narrow the window or the filters). - One export per workspace at a time (
429 export_in_progresswithRetry-After). - Each export is itself recorded (
audit_event.export); theX-Audit-Seqof the response is that event'sseq. - CSV cells that start with
=,+,-,@, a tab or a carriage return are prefixed with'so a spreadsheet does not evaluate them.