# API overview

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

## Base URL

```text
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:

```bash
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`).

| Status | Means |
|---|---|
| `401 UNAUTHORIZED` | missing, mistyped, expired or revoked key |
| `403 FORBIDDEN` | the 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](https://docs.zanora.dev/concepts/accounts-and-keys.md) 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](https://docs.zanora.dev/concepts/money.md).

## 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](mailto:support@zanora.dev). Every code is listed in [Errors and refusals](https://docs.zanora.dev/concepts/errors.md).

## 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:

```bash
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

| Area | Routes |
|---|---|
| [Signup and accounts](https://docs.zanora.dev/api/signup-and-accounts.md) | `POST /v1/signup`, `/verify`, `/resend`, `GET /v1/signup/:id`, `/v1/accounts/*` |
| [Keys and identity](https://docs.zanora.dev/api/keys.md) | `GET /v1/whoami`, `/v1/auth/self`, `/v1/auth/scopes`, `/v1/auth/keys…` |
| [Discovery](https://docs.zanora.dev/api/discovery.md) | `POST /v1/discovery/search` |
| [Capabilities](https://docs.zanora.dev/api/capabilities.md) | `POST/GET /v1/capabilities`, `GET/POST /v1/capabilities/:id`, `…/deprecate` |
| [Providers](https://docs.zanora.dev/api/providers.md) | `GET /v1/providers`, `/:id`, `…/verification-request`, `…/rate`, `…/payout-destination(s)` |
| [Wallets](https://docs.zanora.dev/api/wallets.md) | `/v1/wallets…`: create, list, balance, ledger, top-up, deposit address, freeze |
| [Policies and approvals](https://docs.zanora.dev/api/policies-and-approvals.md) | `/v1/policies…`, `/v1/approvals…` |
| [Receipts](https://docs.zanora.dev/api/receipts.md) | `GET /v1/receipts`, `/:id`, `/v1/transactions/:id/receipts` |
| [Payouts and rails](https://docs.zanora.dev/api/payouts.md) | `POST /v1/payouts`, `GET /v1/rail/providers`, `/v1/rail/transactions` |
| [Platform](https://docs.zanora.dev/api/platform.md) | `GET /health`, `/v1/platform/public-key`, `/v1/platform/receipt-keys/:id` |
| [x402 protocol](https://docs.zanora.dev/api/x402.md) | `POST /v1/x402/challenge`, `/authorize`, `/complete`, `/fail`, for middleware authors |
