Agents consume JSON, not terminal tables. This page is the contract every other page in this section relies on: how to turn JSON on, the envelope shape, where output goes, and the stable codes the CLI guarantees. Read it once and branch on it. (Per-command flags live in the command reference.)
Get machine-readable output#
Pass --json explicitly in scripts. The CLI also switches to JSON automatically when stdout is not a terminal, but an explicit flag keeps logs auditable.
| Trigger | Result |
|---|---|
--json | JSON on stdout. |
--output json / --output table | Forces JSON on, or human tables on. |
| Non-TTY stdout (piped or redirected) | JSON automatically. |
Precedence: --output wins, then --json, then the TTY check (non-terminal → JSON, terminal → human tables).
Data goes to stdout. Diagnostics, progress, and errors go to stderr. Parse stdout; never scrape stderr for results.
Read the success envelope#
Almost every command returns the same two-field envelope. (The few that don't are listed under envelope exceptions.)
schemastringrequiredStable identifier for the output shape, for example repost.events.search/v1. Branch on this. It is versioned and never changes meaning within a major version.
dataobjectrequiredThe command result. Its shape is determined by schema.
repost events search 'method:POST' --bucket stripe-prod --since 15m --limit 2 --jsonGuard on schema, then read data. The same result, parsed two ways. This is your consuming code, not Repost internals:
result=$(repost events search 'method:POST' --bucket stripe-prod --since 15m --json)
# Fail loudly if the shape isn't the one you built against
echo "$result" | jq -e '.schema == "repost.events.search/v1"' >/dev/null
# Read fields; take the next page only when there is one
echo "$result" | jq -r '.data.items[] | select(.response_status >= 500) | .id'
next=$(echo "$result" | jq -r 'if .data.has_more then .data.next_cursor else empty end')Event payloads in data are redacted by default: credential and signature headers and secret-like JSON keys are masked, but PII is not. See Transcript safety for exactly what is and isn't removed.
Know the envelope exceptions#
A few commands do not use the {schema, data} envelope. An agent that assumes a schema field everywhere will break on these.
| Command | Output |
|---|---|
expect | A single bare JSON object (no envelope, no schema). Always JSON, even without --json. |
tail | NDJSON: one bare JSON object per matching event, one per line. Always JSON. |
capabilities | Top-level { schema, version, commands }. Always JSON. |
events get --body-only | The raw request body, no envelope. |
docs schema | { schema, command, output_schema, output_mode, envelope, data_shape? }. |
docs agent / docs search | Markdown text, not JSON. |
Wait commands (expect, tail) are covered in Wait for events. Discovery commands (capabilities, docs schema) are covered in Discovery.
Handle errors#
When JSON is enabled, failures are structured: the error object goes to stderr and the process exits non-zero.
error.codestringrequiredStable machine code. Branch on this, never on the message.
error.messagestringrequiredHuman-readable explanation. For people, not programs.
error.exit_codeintegerrequiredMirrors the process exit status (see the table below).
error.hintstringSuggested next step, when one applies.
error.docsstringAlways present; defaults to repost docs agent.
error.missing_scopestringThe exact scope to request. Present only on forbidden_scope.
error.token_creation_urlstringWhere to mint a token with the missing scope. Present only on forbidden_scope.
repost replay evt_01JZ8V1 --bucket stripe-prod --forwarder prod-api --yes --jsonDecide from error.code and exit_code, never from the human message:
# Capture stdout (data) and stderr (the error envelope) separately
if out=$(repost replay evt_01JZ8V1 --bucket stripe-prod --forwarder prod-api --yes --json 2>err.json); then
echo "$out" | jq '.data'
else
case "$(jq -r '.error.code' err.json)" in
rate_limited|upstream_unavailable) action=retry ;; # the only codes worth retrying
forbidden_scope) action=request_scope ;;
partial_failure) action=inspect ;;
*) action=fail ;;
esac
exit "$(jq -r '.error.exit_code' err.json)"
fierror.code | exit | When | Agent response |
|---|---|---|---|
validation_failed | 2 | Bad input: a client-side flag/argument check or HTTP 400/422 | Fix the command. Do not retry unchanged. |
active_org_required | 2 | A user token can see zero or multiple licensed organizations, so a mutation has no unambiguous org | Select one with repost config org set <slug> or --org <slug>, or use an organization token. |
unauthorized | 3 | Invalid or missing token (401) | Refresh or replace REPOST_TOKEN. |
forbidden_scope | 3 | Token lacks a scope (403) | Request missing_scope or take a read-only path. |
not_found | 4 | Unknown id/bucket/forwarder (404) | Verify the id and active organization. |
quota_exceeded | 5 | Plan limit reached (402) | Not transient. Stop or reduce scope. |
rate_limited | 6 | Server limit (429) | Back off, then retry. |
timeout | 7 | expect / replay wait deadline, or promote setup browser pairing deadline. Not tail, which treats --max-wait as the end of the capture and exits 0. | Report the unmet wait. Inspect state before retrying. |
partial_failure | 8 | health --fail-on breach; replay wait with failures | Inspect details before declaring success. |
conflict | 1 | State changed under you (409/412) | Refresh state, then retry. |
upstream_unavailable | 1 | Service error / unknown (5xx) | Retry with backoff, then report instability. |
validation_failed, active_org_required, forbidden_scope, and quota_exceeded are terminal. Retrying the same command will fail the same way. Only rate_limited and upstream_unavailable are worth retrying.
Pin to a contract version#
Every command schema is repost.<area>/v1. The success envelope is {schema, data}; the failure envelope is {error}. Exit codes 0–8 are stable. When a payload changes incompatibly, the schema suffix bumps (/v2) and both are served during migration, so an agent can pin to the version it was built against.
Output contract (quick reference)#
- Enable JSON:
--json, or--output json, or pipe stdout (non-TTY auto-enables JSON).--outputwins over--json. - Success:
{"schema":"repost.<area>/v1","data":{...}}on stdout. - Failure:
{"error":{"code","message","exit_code","hint?","docs","missing_scope?","token_creation_url?"}}on stderr, non-zero exit. - Exceptions (no
{schema,data}):expect(bare object),tail(NDJSON),capabilities({schema,version,commands}),events get --body-only(raw body),docs schema({schema,command,output_schema,output_mode,envelope}),docs agent/docs search(markdown). - Exit codes: 0 ok · 2 validation_failed/active_org_required · 3 unauthorized/forbidden_scope · 4 not_found · 5 quota_exceeded · 6 rate_limited · 7 timeout · 8 partial_failure · 1 conflict/upstream_unavailable.
- Branch on
error.codeandexit_code, never onmessage. Retry onlyrate_limitedandupstream_unavailable. Output redacts credentials, not PII.
Continue#
Recover from unauthorized and forbidden_scope, and the read / write / secrets model.
What events get / events diff mask in data, and what they leave in the clear.
Pull each command's exact data shape from the binary with docs schema.