Skip to content
Warrantv0.1

Errors

One envelope, a stable code, and a request id on every response.

Every error is the same shape — type, code, message, doc_url, request_id — and nothing else ever leaves this API.

{
  "error": {
    "type": "invalid_request",
    "code": "deadline_in_past",
    "message": "A warrant deadline must be in the future.",
    "doc_url": "https://docs.gowarrant.xyz/api-reference/errors#deadline_in_past",
    "request_id": "req_537c915a-c1dd-4625-93a2-21fa7ec5d938"
  }
}

Branch on code, not on message. Codes are stable; messages are written for people and may be reworded. type maps one-to-one onto the status, so it never tells you anything the status line did not.

An unexpected server-side throw is normalised too. You get type: "api_error", code: "internal_error" and a request id — never a stack trace and never a driver message.

Request ids

Every response carries X-Request-Id, error or not. Send your own and it is echoed rather than replaced, so a request id can be threaded from your logs into ours.

POST /v1/warrants HTTP/1.1
Host: api.gowarrant.xyz
Authorization: Bearer wr_test_8f0c1b4e2b1a4d599d0e3a2f7c1b9e44
Idempotency-Key: 0d8f1a1e-6f2c-4c1a-9a3f-2b6e5d4c3b2a
Content-Type: application/json

{
"amount": 8400000,
"payee": "api.exampledata.io",
"deadline": "2026-08-16T12:00:00.000Z",
"spec": { "clauses": [{ "kind": "http.status", "equals": 200 }] }
}

400

{
"error": {
  "type": "invalid_request",
  "code": "validation_failed",
  "message": "amount: Invalid input: expected string, received number",
  "doc_url": "https://docs.gowarrant.xyz/api-reference/errors#validation_failed",
  "request_id": "req_537c915a-c1dd-4625-93a2-21fa7ec5d938"
}
}
curl -sS https://api.gowarrant.xyz/v1/warrants \
-H "Authorization: Bearer $WARRANT_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
  "amount": 8400000,
  "payee": "api.exampledata.io",
  "deadline": "2026-08-16T12:00:00.000Z",
  "spec": { "clauses": [{ "kind": "http.status", "equals": 200 }] }
}'

That example is the mistake most first integrations make: 8400000 as a JSON number rather than "8400000" as a string. See amounts.

Statuses

StatustypeMeaning
400invalid_requestThe request is malformed or fails validation.
401unauthenticatedNo credentials, or credentials resolving to nobody.
402insufficient_fundsNot enough available balance to hold.
403forbiddenAuthenticated, and refused.
404not_foundNo such thing, or it is another organisation's.
409invalid_state / conflictThe transition is not legal, or an idempotency conflict.
429rate_limitedOver the window. See rate limits.
500api_errorSomething broke on our side.

Codes you are most likely to hit

Validation and shape

codeStatusCause
validation_failed400A field failed its schema. The message names the path.
deadline_in_past400The deadline is not in the future.
idempotency_key_required400A money-moving POST arrived without the header.

Authentication and permission

codeStatusCause
not_authenticated401No credentials, or a key or cookie resolving to nobody.
insufficient_role403Your role is below what the route needs.
user_session_required403An API key tried something that needs a person.
kill_switch_active403The organisation has stopped new warrants.
passkey_unavailable403Above the threshold, and no verifier is configured.

Money and state

codeStatusCause
insufficient_funds402Available balance will not cover the hold.
warrant_not_found404No such warrant, or not yours.
illegal_transition409e.g. releasing an already-refunded warrant.
version_conflict409Someone else moved it first. Re-read and retry.
idempotency_key_reused409Same key, different body.

Sessions and policies

codeStatusCause
session_ceiling_exceeded403The spend session is out of headroom.
session_warrant_limit_reached403The session has opened its allowance.
session_failure_budget_spent403Consecutive failures exhausted the budget.
session_expired / session_revoked403The spend session is over.
policy_no_bands400A policy needs at least one band.
policy_no_floor_band400A policy needs a band at minScore: 0.
policy_band_bps_required400A partial_release band needs releasedBps in 0–10000.
policy_band_out_of_range400minScore outside 0–1.

Internal surfaces

These sit outside /v1 and authenticate with a named header.

codeStatusCause
invalid_worker_secret401X-Warrant-Worker absent or wrong.
worker_secret_unset403No secret configured; the route is closed.
telegram_webhook_unauthenticated401Webhook secret absent or wrong.
telegram_webhook_unset403No secret configured; the webhook is closed.

Amounts are integer strings

Every amount on the wire is a string of digits in the asset's smallest unit, validated against ^[0-9]+$.

"8400000"    8.40 USDC
8400000      rejected — a JSON number
"8.40"       rejected — not the smallest unit

9007199254740993 as a JSON number silently loses precision as a double, and this is a payments API. The decimal point is a display concern.