
# Errors

The API uses conventional HTTP status codes and returns a JSON body with an `error` field (and sometimes a `message`) on failure.

```json
{ "error": "business_not_approved", "message": "Your business must be approved …" }
```

## Status codes

| Status | Meaning | Typical causes |
|---|---|---|
| `200` | OK | Successful `GET`/`upload`. |
| `202` | Accepted | Verification queued (see [create verification](https://trust.myaza.co/documentation/api-create-verification/markdown)). |
| `400` | Bad request | Invalid body, bad/missing field, unsupported file type, file too large. |
| `401` | Unauthorized | Missing `Authorization` header, or invalid/revoked API key. |
| `403` | Forbidden | Production key without an approved business, a publishable key on a secret-only endpoint (`secret_key_required`), or a `requestId` owned by another org. |
| `404` | Not found | Verification ID does not exist (or isn't yours). |
| `429` | Too many requests | [Rate limit](https://trust.myaza.co/documentation/rate-limits/markdown) exceeded. |
| `500` | Server error | E.g. pricing not configured for the requested country/ID type. |

## Common error bodies

| `error` | Status | Meaning |
|---|---|---|
| `Missing or invalid Authorization header` | 401 | No Bearer token sent. |
| `Invalid API key` | 401 | Key is unknown or revoked. |
| `Invalid request body` | 400 | Verify payload failed validation; see `message` for details. |
| `Forbidden` | 403 | The `requestId` belongs to a different organisation. |
| `business_not_approved` | 403 | Production access requires an [approved business](https://trust.myaza.co/documentation/environments/markdown#production). |
| `secret_key_required` | 403 | A publishable (`pk_`) key was used on a secret-only endpoint (full result or media). Use a secret (`sk_`) key from your backend. See [authentication](https://trust.myaza.co/documentation/authentication/markdown). |
| `environment_mismatch` | 403 | The key's environment doesn't match the server it was sent to (e.g. a `pk_live_` key against the sandbox host). Use the base URL for the key's [environment](https://trust.myaza.co/documentation/environments/markdown). |
| `Verification not found` | 404 | Unknown verification ID. |
| `File too large (max 25MB)` | 400 | Upload exceeded the size cap. |
| `Too many requests, please try again later.` | 429 | Slow down; see [rate limits](https://trust.myaza.co/documentation/rate-limits/markdown). |
| `pricing_not_configured` | 500 | No price set for that country/ID type. Contact Myaza. |

## Asynchronous failures

A `202 Accepted` from [create verification](https://trust.myaza.co/documentation/api-create-verification/markdown) only means the request was queued. The verification can still finish as `failed`, `not_found`, or `error`. Those are **not** HTTP errors: read them from [`GET /status/:id`](https://trust.myaza.co/documentation/api-verification-status/markdown) or the [`verification.*` webhooks](https://trust.myaza.co/documentation/webhooks/markdown).

Each non-success outcome carries a human-readable `reason` plus a stable `reasonCode` you can branch on (e.g. `document_expired`, `selfie_mismatch`, `identity_not_found`). The full catalogue is in [failure reason codes](https://trust.myaza.co/documentation/verifications/markdown#failure-reason-codes).

## Health check

```
GET /api/kyc/health
```

A public, unauthenticated endpoint for uptime monitoring.

```json
{ "status": "ok" }
```

## Handling errors well

- Retry `429`, `502`, `503`, `504` and connection failures with backoff and the
  same idempotency key. Do not blindly retry every `500`: preserve its request ID
  and contact support if it persists.
- Treat `4xx` (except `429`) as **non-retryable**: fix the request; retrying unchanged will fail again.
- Always read the `error`/`message` fields; don't rely on status code alone.

## Trust SDK errors

`@myazahq/trust-sdk` exposes typed errors so your backend can branch without
parsing messages:

```js
import {
  AuthenticationError,
  IdempotencyConflictError,
  InsufficientCreditError,
  MyazaError,
  RateLimitError,
  ValidationError,
} from '@myazahq/trust-sdk';

try {
  await myaza.transactions.retrieve('activity_123');
} catch (error) {
  if (error instanceof RateLimitError) {
    console.log('Retry after seconds:', error.retryAfterSeconds);
  } else if (error instanceof ValidationError || error instanceof IdempotencyConflictError) {
    console.error(error.code, error.details, error.requestId);
  } else if (error instanceof InsufficientCreditError) {
    console.log({ pending: error.pending, statusUrl: error.statusUrl });
  } else if (error instanceof AuthenticationError) {
    console.error('Check the key type and environment.');
  } else if (error instanceof MyazaError) {
    console.error(error.status, error.code, error.requestId);
  }
}
```

Transaction and activity decisions fail closed when credit is unavailable; a
late record is not an authorisation response. A paused customer risk assessment
sets `InsufficientCreditError.pending` or returns `pending_credit`, keeps the same
resource and resumes after funding. Do not submit a duplicate.
