ZZanoraDocs

Using the API

Base URL, authentication, amount format, errors, rate limits and pagination for every Zanora API route.

Base URL

Output
https://api.zanora.dev

The API is HTTPS and JSON only. A request that reaches the gateway over plain HTTP is refused with 403 INSECURE_TRANSPORT before authentication, so a key sent over HTTP isn't accepted.

Authentication

Send your API key in the x-api-key header on every request:

Terminal
curl -s https://api.zanora.dev/v1/whoami -H "x-api-key: zk.akey_….…"

These routes need no key: GET /health, the four signup routes, and the three account-recovery routes (login, password/forgot, password/reset).

StatusMeans
401 UNAUTHORIZEDmissing, mistyped, expired or revoked key
403 FORBIDDENthe key lacks the route's scope (the message names it), or the record belongs to another tenant

Each route below lists the scope it needs. See Accounts and API keys for the full scope table.

Tenancy comes from the key

Routes work out your providerId or workspaceId from your key. Don't send them in request bodies. They're ignored for tenant keys, and a query that names another tenant changes nothing. List routes return your records only.

Amounts

Every amount is a string of integer minor units (cents): "priceMinor": "100" is $1.00. A JSON number is rejected with 400 VALIDATION_FAILED, never rounded. Responses use the same format. See Money and amounts.

Request bodies

  • Send content-type: application/json only when there's a body. An empty body with that header is rejected.
  • Routes whose body is optional accept no body at all.
  • Unknown enum values are rejected, not ignored.

Errors

JSON
{ "error": { "code": "VALIDATION_FAILED", "message": "priceMinor must be a string of integer minor units", "details": {} } }

Handle errors by code. An unexpected error returns 500 with "internal error" and an errorId. Quote the errorId when you contact support@zanora.dev. Every code is listed in Errors and refusals.

Rate limits

Requests are limited per key (or per IP without one), 50 per second by default. Over the limit, you get 429 RATE_LIMITED with a retry-after header. Signup and password-recovery routes have a separate, stricter per-IP limit. If you have many workers, give each its own key rather than sharing one.

Pagination

Lists that grow over time use a cursor, not an offset:

Terminal
curl -s "https://api.zanora.dev/v1/receipts?limit=50" -H "x-api-key: $KEY"
# → { "receipts": [ … ], "nextCursor": "rcp_…" }
curl -s "https://api.zanora.dev/v1/receipts?limit=50&after=rcp_…" -H "x-api-key: $KEY"

nextCursor is present only when the page came back full. An unknown cursor is refused rather than starting again from the top.

Route index

AreaRoutes
Signup and accountsPOST /v1/signup, /verify, /resend, GET /v1/signup/:id, /v1/accounts/*
Keys and identityGET /v1/whoami, /v1/auth/self, /v1/auth/scopes, /v1/auth/keys…
DiscoveryPOST /v1/discovery/search
CapabilitiesPOST/GET /v1/capabilities, GET/POST /v1/capabilities/:id, …/deprecate
ProvidersGET /v1/providers, /:id, …/verification-request, …/rate, …/payout-destination(s)
Wallets/v1/wallets…: create, list, balance, ledger, top-up, deposit address, freeze
Policies and approvals/v1/policies…, /v1/approvals…
ReceiptsGET /v1/receipts, /:id, /v1/transactions/:id/receipts
Payouts and railsPOST /v1/payouts, GET /v1/rail/providers, /v1/rail/transactions
PlatformGET /health, /v1/platform/public-key, /v1/platform/receipt-keys/:id
x402 protocolPOST /v1/x402/challenge, /authorize, /complete, /fail, for middleware authors