> ## Documentation Index
> Fetch the complete documentation index at: https://docs.peridot.id/llms.txt
> Use this file to discover all available pages before exploring further.

# Errors

> Error format, status codes, and rate limits

All errors are returned as JSON with a consistent shape.

## Error format

```json theme={null}
{
  "statusCode": 401,
  "message": "Unauthorized"
}
```

`message` is a string for a single error. When validation fails it can be an array of
strings:

```json theme={null}
{
  "statusCode": 400,
  "message": ["displayName must be shorter than or equal to 64 characters"]
}
```

## Status codes

| Status | Meaning |
| - | - |
| `200` | Success |
| `204` | Success, no content (e.g. `POST /v1/auth/refresh`) |
| `302` | Redirect (Google OAuth flow) |
| `400` | Validation error — one or more fields are invalid |
| `401` | Missing, expired, or invalid access/refresh token |
| `403` | Authenticated but forbidden — e.g. withdrawals disabled, admin-only route, revoked grant |
| `404` | Identity, profile, or wallet does not exist |
| `409` | Conflict — e.g. email already associated with another account, PID already taken, or credential already linked |
| `410` | Gone — claim ticket expired; sign in again for a fresh one |
| `429` | Rate limit exceeded |
| `503` | Google OAuth is not configured on the server |

## Authentication errors

A `401` from a protected endpoint means the access token is missing or expired. Recover by
calling `POST /v1/auth/refresh`. If refresh itself returns `401`, the session is
unrecoverable and the user must sign in again.

## Rate limits

Auth endpoints are rate limited to protect against brute force:

| Endpoint | Limit |
| - | - |
| `POST /v1/auth/refresh` | 10 requests / minute |
| `POST /v1/auth/login` | 20 requests / minute |
| `POST /v1/auth/logout` | 20 requests / minute |
| `POST /v1/auth/exchange` | 20 requests / minute |
| `POST /v1/auth/authorize` | 20 requests / minute |
| `POST /v1/apps` | 10 requests / minute |
| `GET /v1/apps` | 30 requests / minute |
| `PATCH /v1/apps/:id` | 20 requests / minute |
| `POST /v1/apps/:id/secret` | 5 requests / minute |
| `POST /v1/apps/:id/webhook` | 5 requests / minute |
| `GET /v1/apps/:id/fees` | 30 requests / minute |
| `PUT /v1/apps/:id/fees/:operation` | 20 requests / minute |
| `POST /v1/auth/token` | 20 requests / minute |
| `POST /v1/fiat/deposits/checkout` | 10 requests / minute |
| `POST /v1/fiat/transfers/inquiry`, `…/confirm` | 10 requests / minute |
| `POST /v1/fiat/deposits/:id/sync` | 12 requests / minute |
| `GET /v1/auth/google`, `/v1/auth/google/callback` | 100 requests / minute |
| `POST /v1/wallet` | 10 requests / minute |

Exceeding a limit returns `429`. Clients should back off and retry.

## Fiat errors

Fiat endpoints return the same JSON shape, but the **SDK fiat methods throw**
(a plain `Error` whose message is the server message) instead of returning the
`T | ApiError` union that auth/wallet reads use. Handle them with `try/catch`:

```ts theme={null}
try {
  await peridot.fiat.transferConfirm(inquiryId);
} catch (e) {
  // e.message: "Insufficient internal credit", "Amount too small…", etc.
}
```

Common fiat statuses: `400` (bad amount / net too small), `401` (no session),
`404` (unknown recipient / transaction), `503` (ledger disabled or frozen).

## SDK popup errors

Trust-critical SDK calls in popup mode throw instead of returning unions:

| Error | Meaning |
| - | - |
| `PopupBlockedError` | Browser blocked the popup — ask the user to allow popups, then retry |
| `PopupClosedError` | User closed the window, or it timed out (login 5 min, approval 2 min) |
| `PopupUnavailableError` | No `popupBaseUrl` set, or not running in a browser |
| `Error("…rejected…")` | User pressed Deny, or the host rejected a malformed/tampered request |

A closed or timed-out popup never resolves — treat it as rejected.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.