Tether
DocsGitHub

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"
ParameterDefaultNotes
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.
limit100Clamped 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

eventRecorded when
proposedA write was staged, by a model or over the API
approvedA person confirmed it. With a policy that needs several approvers, one per person
rejectedA person declined it; reason is the reason they gave with the rejection
expiredThe confirmation window passed: recorded once, by the scheduled expiry sweep or by a confirm that arrived late, whichever noticed first. userId is 0
executedYour endpoint ran it and returned success. This is the billable event
failedYour endpoint ran it and it did not succeed
replayedA repeat confirm matched an earlier success and returned that result; nothing ran again
supersededThis person approved, but another confirm had already claimed the action and executed it
prohibitedRefused under the child-safety policy; no person can clear it
precondition_failedApproved, 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_errorApproved, but the precondition's read failed when it came to execute. The write was not called and the action is still pending
provider_refusedApproved and sent, and your imported API refused it without applying it; reason carries your status, code and words. Settled, never retried
verifiedAfter it executed, the write's verify read showed its effect. userId is 0: Tether checked
unverifiedAfter 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.

  • 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