API reference
Audit
Every transition in the life of a staged write is recorded once and never changed: proposed, by whom; approved or rejected, by whom; executed, with what outcome. This is the endpoint that reads it back. Tenant secret key only.
Query the audit
GET /v1/audit?decision=executed&from=2026-09-01&limit=100
curl "$TETHER_URL/v1/audit?tool=refund_order&from=2026-09-01" \
-H "X-Api-Key: $TETHER_SECRET_KEY"
| Parameter | Default | Notes |
|---|---|---|
decision | — | Only this event type. See the table below. |
tool | — | Only this tool. |
user | — | Only events by this userId. |
subject | — | Only events about this verified customer, your own id for them. |
scope | — | Only events staged under this scope id. |
from | — | Events at or after this time. A date such as 2026-09-01 means midnight UTC; a full timestamp such as 2026-09-01T09:30:00Z is exact. |
to | — | Events before this time, same formats. |
after | — | Events with an id greater than this, for paging. |
limit | 100 | Clamped to 1–1000. |
Filters combine with AND. A from or to that is not a date answers 400.
{
"events": [
{
"id": 184,
"occurredAt": "2026-09-30T06:33:49.99Z",
"actionId": "1530739d-0526-4801-9001-b493f9c77da0",
"tool": "cancel_order",
"event": "approved",
"userId": 6,
"user": { "id": 31, "userId": 6, "displayName": "Finance", "email": "finance@yourshop.com" },
"source": "api",
"payloadHash": "f23671a25949da080ca9ddaad13ba79e19bfd6f13818b1dfef04c35d75400e3e",
"subject": "G1",
"reason": null
}
],
"count": 1,
"nextAfter": null
}
Events come oldest first. When a page is full, nextAfter is the last id on it; pass it as
after for the next page. It is null on the last page.
userId is the person who acted, from X-Tether-User-Id, and 0 where no person did, such as
proposed on a write staged over the API, or where the person has no id in your product (someone
invited by email who approved in the inbox). user names the
person from your own list of registered people: id (the id Tether gave them),
userId, displayName and email, or null for a rule, the system, or an id nobody is registered
with. It is read from your workspace's list only. The CSV keeps its columns and has no user. source says which route the event came through.
payloadHash is a SHA-256 of what the action would run with, never the payload itself, so the
audit can be shared without exposing the data and still proves nothing changed. subject is the
verified customer the action was staged for, on every event of
that action including a member of staff's later approval, and null for an action staged without
one. reason says why, on the events that have one: verified and unverified carry what the
verification read found, and rejected the reason the person gave, if any. It is null on every
other event.
The events
event | Recorded when |
|---|---|
proposed | A write was staged, by a model or over the API |
approved | A person confirmed it. With a policy that needs several approvers, one per person |
rejected | A person declined it; reason is the reason they gave with the rejection |
expired | The confirmation window passed: recorded once, by the scheduled expiry sweep or by a confirm that arrived late, whichever noticed first. userId is 0 |
executed | Your endpoint ran it and returned success. This is the billable event |
failed | Your endpoint ran it and it did not succeed |
replayed | A repeat confirm matched an earlier success and returned that result; nothing ran again |
superseded | This person approved, but another confirm had already claimed the action and executed it |
prohibited | Refused under the child-safety policy; no person can clear it |
precondition_failed | Approved, but the write's precondition no longer held when it came to execute. The write was never called; reason says what the read found |
precondition_error | Approved, but the precondition's read failed when it came to execute. The write was not called and the action is still pending |
provider_refused | Approved and sent, and your imported API refused it without applying it; reason carries your status, code and words. Settled, never retried |
verified | After it executed, the write's verify read showed its effect. userId is 0: Tether checked |
unverified | After it executed, the verify read did not show the effect, failed, or did not answer in time; reason says which. The action is still executed and is not run again, and a person was handed the outcome |
Two confirms racing one action read as approved, approved, executed, superseded: both
people did approve, and only one approval became a write. A confirm refused before it is
recorded as an approval, such as one missing the risk acknowledgement, leaves no event.
Export as CSV
curl "$TETHER_URL/v1/audit?from=2026-09-01" \
-H "X-Api-Key: $TETHER_SECRET_KEY" \
-H "Accept: text/csv" > audit.csv
Send Accept: text/csv and the same filters return a file instead of JSON. The columns are fixed,
in this order:
id,occurred_at,action_id,tool,event,user_id,source,payload_hash,subject,reason
subject is empty for an action staged without a verified customer, and reason for an event
that has none. Filter with decision=unverified to list every outcome a person should check.
The same query returns the same bytes each time, so an export can be checksummed and compared.
Append-only
There is no route to change or delete an event. The executed event is written in the same
transaction as the action's outcome, so the trail cannot say an action ran without the action
saying so too.
Related
- Actions — the confirm, reject and reconcile calls that produce these events
- Approvers and policy — who is allowed to produce an
approved - Outcomes — these events counted per period, with the conversations behind them