> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.stakaba.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.stakaba.com/_mcp/server.

# Error Handling

All errors return a consistent JSON body and an appropriate HTTP status code.

## Error format

```json
{
  "error": "VALIDATION_ERROR",
  "message": "grossAmount must be positive",
  "details": {}
}
```

## HTTP status codes

| Status | `error` code       | When                                     |
| ------ | ------------------ | ---------------------------------------- |
| `400`  | `VALIDATION_ERROR` | Request body is invalid                  |
| `401`  | `UNAUTHORIZED`     | Missing or invalid `x-api-key`           |
| `403`  | `FORBIDDEN`        | Key exists but lacks permission          |
| `404`  | `NOT_FOUND`        | Resource doesn't exist                   |
| `502`  | `PROVIDER_ERROR`   | Payment provider rejected the request    |
| `500`  | `APP_ERROR`        | Stakaba internal error — contact support |

## Common errors

### Invalid API key

```json
{ "error": "UNAUTHORIZED", "message": "Invalid merchant API key" }
```

Check that you're sending the key in the `x-api-key` header (not `Authorization`).

### Amount too small

```json
{ "error": "VALIDATION_ERROR", "message": "grossAmount must be positive" }
```

Mobile money operators require a minimum of \~500 TZS. Card payments have no set minimum.

### Invalid network

```json
{ "error": "VALIDATION_ERROR", "message": "network must be Mpesa, TigoPesa, AirtelMoney, or HaloPesa" }
```

The `network` field is case-sensitive. Use the exact values from the [networks list](/mobile-money-collections).

### Payment unavailable

```json
{ "error": "PROVIDER_ERROR", "message": "Payouts are temporarily unavailable. Please try again in a few minutes." }
```

A temporary issue on the operator's side. Retry the request after a short wait.

### Invalid mobile number

```json
{ "error": "PROVIDER_ERROR", "message": "The recipient number is not registered for mobile money on this network." }
```

The number exists but is not active on the selected network. Verify the number and network are correct.

## Retrying requests

Safe to retry on `502` and `500` errors — use exponential backoff (1s, 2s, 4s). Do not retry `400` or `401` — these indicate a problem with your request that retrying won't fix.

Each payment request generates a unique `internalReference`. If you retry, check first whether the original payment succeeded using `GET /api/v1/transactions/{reference}`.