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

# Errors & Retries

> Understand Orafi error codes, response formats, and how to implement safe retry logic.

The Orafi API uses conventional HTTP status codes and returns a consistent JSON error body so you can handle failures programmatically.

***

## Error response format

Every error response follows this structure:

```json theme={null}
{
  "success": false,
  "message": "A human-readable explanation of the error."
}
```

***

## HTTP status codes

### Client errors (4xx)

| Code  | Meaning                                                 | What to do                                         |
| ----- | ------------------------------------------------------- | -------------------------------------------------- |
| `400` | **Bad Request** — Invalid parameters or malformed body. | Check the request payload against the docs.        |
| `401` | **Unauthorized** — Missing or invalid API key.          | Verify the `x-api-key` header.                     |
| `403` | **Forbidden** — Your account lacks permission.          | Complete onboarding or check mode.                 |
| `404` | **Not Found** — The resource doesn't exist.             | Verify the ID or path.                             |
| `429` | **Too Many Requests** — Rate limit exceeded.            | Back off and retry after the `Retry-After` header. |

### Server errors (5xx)

| Code  | Meaning                                                     | What to do                      |
| ----- | ----------------------------------------------------------- | ------------------------------- |
| `500` | **Internal Server Error** — Unexpected failure on our side. | Retry with exponential backoff. |
| `502` | **Bad Gateway** — An upstream dependency failed.            | Retry after a short delay.      |
| `503` | **Service Unavailable** — Temporary maintenance.            | Retry after a short delay.      |

<Note>
  **Never retry 4xx errors automatically** (except `429`). Fix the request first.
</Note>

***

## Common failure scenarios

### Payment failures

| Scenario             | Cause                                       | Resolution                            |
| -------------------- | ------------------------------------------- | ------------------------------------- |
| Insufficient balance | Payout or refund exceeds available funds.   | Check balances before initiating.     |
| Network congestion   | On-chain confirmation delayed.              | Wait and poll or rely on webhooks.    |
| Expired payment      | Customer didn't pay within the time window. | Create a new payment.                 |
| Invalid address      | Deposit address format incorrect.           | Verify the address before submitting. |

***

## Retry strategy

### Idempotency via `txRef`

Every payment accepts a `txRef` (transaction reference) that acts as an **idempotency key**. Sending the same `txRef` twice returns the original payment — no duplicate charge is created.

### Exponential backoff

For server errors (`5xx`) and rate limits (`429`), implement exponential backoff:

<CodeGroup>
  ```javascript Node.js theme={null}
  async function createPaymentWithRetry(payload, maxRetries = 3) {
    for (let attempt = 1; attempt <= maxRetries; attempt++) {
      try {
        const res = await fetch("https://api.orafi.app/transactions/payment/create", {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
            "x-api-key": process.env.ORAFI_API_KEY,
          },
          body: JSON.stringify(payload),
        });

        if (res.ok) return await res.json();

        // Don't retry client errors (except 429)
        if (res.status >= 400 && res.status < 500 && res.status !== 429) {
          throw new Error(`Client error ${res.status}: ${(await res.json()).message}`);
        }

        if (attempt === maxRetries) {
          throw new Error(`Max retries reached (${res.status})`);
        }

        await new Promise((r) => setTimeout(r, Math.pow(2, attempt) * 1000));
      } catch (err) {
        if (attempt === maxRetries) throw err;
        await new Promise((r) => setTimeout(r, Math.pow(2, attempt) * 1000));
      }
    }
  }
  ```

  ```python Python theme={null}
  import time, requests

  def create_payment_with_retry(payload, max_retries=3):
      for attempt in range(1, max_retries + 1):
          try:
              res = requests.post(
                  "https://api.orafi.app/transactions/payment/create",
                  headers={
                      "Content-Type": "application/json",
                      "x-api-key": ORAFI_API_KEY,
                  },
                  json=payload,
              )

              if res.ok:
                  return res.json()

              if 400 <= res.status_code < 500 and res.status_code != 429:
                  raise Exception(f"Client error {res.status_code}: {res.json()['message']}")

              if attempt == max_retries:
                  raise Exception(f"Max retries reached ({res.status_code})")

              time.sleep(2 ** attempt)
          except Exception:
              if attempt == max_retries:
                  raise
              time.sleep(2 ** attempt)
  ```
</CodeGroup>

***

## Webhook retry behavior

Orafi automatically retries failed webhook deliveries. To ensure reliable processing:

* **Return a `2xx` status** within 10 seconds to acknowledge receipt.
* **Handle duplicates** — your handler should be idempotent. Use the `transactionId` to deduplicate.
* **Check delivery history** — Use the [Webhook Deliveries](/api-reference/endpoint/webhook-deliveries) endpoint to inspect attempt counts and response statuses.
