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

# Errors

> Error format and codes shared by all GoPay APIs

# Errors

All GoPay APIs return errors in a single, stable shape. `code` is machine-readable and safe to program against; `message` is human-readable; `requestId` identifies the request in GoPay's logs — include it in support tickets.

```json theme={null}
{
    "error": {
        "code": "VALIDATION_FAILED",
        "message": "amount must be greater than 0; notifyUrl must use https",
        "requestId": "req_..."
    }
}
```

Validation errors are **accumulated**: the message lists every failing field, not just the first, so you can fix all issues in one pass.

## Error Codes

| Code                   | HTTP Status | Meaning                                                                                                        |
| ---------------------- | ----------- | -------------------------------------------------------------------------------------------------------------- |
| `INVALID_REQUEST`      | 400         | Body is not valid JSON or is missing required fields                                                           |
| `VALIDATION_FAILED`    | 400         | One or more fields failed validation; message lists them all                                                   |
| `MISSING_AUTH_HEADERS` | 401         | One of `X-Public-Key`, `X-Signature`, `X-Timestamp`, `X-Request-ID` is missing                                 |
| `INVALID_SIGNATURE`    | 401         | Authentication failed (generic — covers bad signature, unknown key, expired key, stale timestamp, and similar) |
| `UNAUTHORIZED`         | 401         | Not authorized                                                                                                 |
| `PERMISSION_DENIED`    | 403         | Key lacks the required permission (`c2b`/`b2c`) or client IP is not allowlisted                                |
| `INSUFFICIENT_FUNDS`   | 402         | Transfers only — merchant wallet balance cannot cover the payout                                               |
| `IDEMPOTENCY_CONFLICT` | 409         | `X-Request-ID` was already used (replay / duplicate request)                                                   |
| `RATE_LIMITED`         | 429         | Per-merchant rate limit exceeded                                                                               |
| `INTERNAL`             | 500         | Unexpected server error                                                                                        |
| `UPSTREAM_UNAVAILABLE` | 503         | A downstream service is temporarily unavailable                                                                |
| `UPSTREAM_TIMEOUT`     | 504         | A downstream call timed out                                                                                    |

<Note>
  Authentication failures are deliberately generic. `INVALID_SIGNATURE` covers every authenticity check — bad signature, unknown or inactive key, merchant mismatch, stale timestamp — so responses never reveal which specific check failed.
</Note>

## Retry Guidance

**Payments (C2B)** — on `UPSTREAM_UNAVAILABLE` or `UPSTREAM_TIMEOUT`, retry with a **fresh** `X-Request-ID` and signature (the old ID may already be consumed), and send the **same** `Idempotency-Key` so the payment is not created twice.

**Transfers (B2C)** — retrying moves money, so be careful. On a timeout, check for a settlement webhook or contact support with the `requestId` before resubmitting, and always use a stable `client_reference` so duplicates surface during reconciliation. The wallet debit itself is idempotent per transaction, so a single accepted transfer can never double-charge.

**Never retry** `INVALID_SIGNATURE`, `PERMISSION_DENIED`, or `VALIDATION_FAILED` without fixing the underlying problem — see [Common Mistakes](/getting-started/authentication#common-mistakes) for the usual signature culprits.

**Back off** on `RATE_LIMITED` (429) before retrying; your per-merchant budget refills over time.
