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

# Errors

> Error codes, response format, and retry strategy

All SandPay errors follow a single, stable format. No surprises, no ad-hoc shape depending on the endpoint.

## Response format

```json theme={null}
{
  "error": "invalid_body",
  "message": "amount must be greater than 0",
  "detail": {
    "fieldErrors": {
      "amount": ["Number must be greater than 0"]
    }
  }
}
```

* `error` — machine-readable code, stable — use it in your logic (`switch`, `if`).
* `message` — human-readable text, in English, subject to change.
* `detail` — optional structured payload (per-field Zod errors, additional context).

The `X-Request-Id` header is included on every response (success and error alike). Keep it if you open a support ticket.

## Codes by HTTP status

| Status | `error`           | When                                                                               |
| ------ | ----------------- | ---------------------------------------------------------------------------------- |
| 400    | `invalid_body`    | The JSON body fails schema validation (Zod). `detail` contains the failing fields. |
| 400    | `invalid_params`  | Invalid query parameters (e.g. `limit=999`).                                       |
| 401    | `unauthorized`    | `Authorization` header is missing.                                                 |
| 401    | `invalid_api_key` | Key is unknown, revoked, or malformed.                                             |
| 402    | `quota_exceeded`  | Monthly quota reached on the current plan. Upgrade your plan.                      |
| 404    | `not_found`       | Resource not found (transaction, application, client…).                            |
| 404    | `env_not_found`   | The `country`/`operator` pair has no configured environment.                       |
| 429    | `rate_limited`    | Too many requests — retry after the `Retry-After` header.                          |
| 5xx    | `internal`        | Error on SandPay's side. Tracked in Sentry, retry recommended.                     |

## Retry strategy

| Status           | Retriable? | Notes                                                     |
| ---------------- | ---------- | --------------------------------------------------------- |
| 4xx (except 429) | No         | Fix the request. Retrying will not help.                  |
| 429              | Yes        | Honour `Retry-After`, then exponential backoff.           |
| 5xx              | Yes        | Exponential backoff: 1s, 2s, 4s, 8s, 16s. Max 5 attempts. |

<Tip>
  The Node SDK raises a `SandPayApiError` that exposes `status`, `code`, `message`, `detail`, and `requestId` — use it to wire up your retry logic.
</Tip>

## Example — handling a quota exceeded error

```ts theme={null}
import { SandPay, SandPayApiError } from "@drwintech/sandpay";

const sp = new SandPay({ apiKey: process.env.SANDPAY_API_KEY! });

try {
  await sp.payments.create({ /* ... */ });
} catch (err) {
  if (err instanceof SandPayApiError && err.code === "quota_exceeded") {
    // Notify the team, trigger an upgrade, etc.
    notifyOps("SandPay quota exceeded, requestId=" + err.requestId);
    return;
  }
  throw err;
}
```
