export const errorShapeCode = `{
  "error": "Human-readable message",
  "code": "STABLE_CODE",
  "request_id": "req_..."
}`

Every error response from the MiniMoth API has a stable `code` field you can use in your code to handle specific cases. Codes will not change — you can safely switch on them.

<CodeBlock code={errorShapeCode} lang="json" />

## Authentication

| Code | Status | Description |
|---|---|---|
| `MISSING_API_KEY` | 401 | No `X-Api-Key` header was sent. |
| `INVALID_API_KEY` | 401 | The API key is not recognised. Check the key in your project settings. |

## Validation

| Code | Status | Description |
|---|---|---|
| `INVALID_PHONE` | 422 | Phone number is not a valid Indian mobile number. Must be 10 digits starting with 6–9, with or without `+91`. |
| `INVALID_OTP_CODE` | 422 | The `code` field must be exactly 6 digits. |
| `MISSING_ACCESS_TOKEN` | 422 | The request body is missing the `access_token` field. |
| `MISSING_REFRESH_TOKEN` | 422 | The request body is missing the `refresh_token` field. |

## OTP

| Code | Status | Description |
|---|---|---|
| `OTP_RATE_LIMITED` | 429 | Too many OTPs requested for this number. Limit is 3 per phone per 10 minutes. |
| `OTP_NOT_FOUND` | 400 | No active OTP exists for this phone number. It may have expired (10 min TTL) or already been used. |
| `INVALID_OTP` | 400 | The OTP code is incorrect. |
| `VERIFY_RATE_LIMITED` | 429 | Too many incorrect attempts for this OTP. The OTP is now locked — request a new one. |
| `SMS_FAILED` | 500 | Both WhatsApp and SMS fallback delivery failed. The charge was not applied. Retry after a moment. |
| `OTP_REQUEST_NOT_FOUND` | 404 | No OTP request found with that ID, or it does not belong to this project. Returned by `GET /v1/otp/status/:otp_id`. |

## Billing

| Code | Status | Description |
|---|---|---|
| `INSUFFICIENT_BALANCE` | 402 | Wallet balance is too low to send an OTP. Top up from the dashboard. No OTP was sent and no charge was applied. |

## Session

| Code | Status | Description |
|---|---|---|
| `SESSION_EXPIRED` | 401 | The refresh token has expired or is invalid. The user must re-authenticate. |
| `INVALID_ACCESS_TOKEN` | 401 | The access token is invalid or expired. Returned by `session/logout` when called with an expired token — refresh the session first, then retry the logout. |
| `RATE_LIMITED` | 429 | General rate limit exceeded — 1000 requests per minute per API key. Back off and retry. |

## Identity

| Code | Status | Description |
|---|---|---|
| `IDENTITY_DELETE_DISABLED` | 403 | Identity deletion isn't turned on for this project. Enable it from the project's Settings card in the dashboard. See [Identity](/docs/identity#deleting-an-identity). |
| `IDENTITY_NOT_FOUND` | 404 | No identity with that id, or it belongs to a different project. |

## Node.js SDK

[@minimoth/sdk-node](https://www.npmjs.com/package/@minimoth/sdk-node) surfaces every code above as-is via `err.code` — plus a few additional codes on top of the API's, for conditions the SDK catches locally before or instead of a network call.

| Code | Status | Description |
|---|---|---|
| `INVALID_PHONE` | — | SDK-only trigger, thrown client-side before any request is sent: spaces or hyphens in the phone number, or an empty string. Distinct from the API's own `INVALID_PHONE` (422) above, which fires server-side on a malformed but well-formatted number. |
| `NETWORK_ERROR` | — | The underlying `fetch()` call threw — the network was unreachable or the request otherwise never completed. |
| `UNKNOWN_ERROR` | — | The API returned an error code the SDK doesn't recognise — the SDK version may be older than the API. Check `err.statusCode` for the raw HTTP status. |

---

Full documentation index: https://minimoth.dev/llms.txt
