# Error Handling (https://docs.chargeflow.io/docs/reference/api-fundamentals/error-handling)



This page explains the HTTP error codes returned by the Chargeflow API, which of them are safe to retry, and how to retry correctly.

## Retry safety at a glance [#retry-safety-at-a-glance]

| Status | Cause                                                                              | Remedy                                                                    | Retry-safe?             |
| ------ | ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------- | ----------------------- |
| `400`  | Malformed JSON, wrong types, missing fields                                        | Fix the request body/params                                               | No - fix first          |
| `401`  | Missing/invalid `x-api-key`, bad HMAC signature                                    | Fix credentials                                                           | No - fix first          |
| `403`  | Malformed signature/headers, key lacks permission for the resource, or key invalid | Diagnose via health check (below)                                         | No - fix first          |
| `404`  | Wrong URL or ID, or resource deleted                                               | Verify the URL and ID                                                     | No - fix first          |
| `429`  | Rate limit exceeded                                                                | Back off and retry; honor `Retry-After` if present                        | **Yes** - after waiting |
| `500`  | Unexpected server error                                                            | Retry with backoff; contact support with `requestId` if persistent        | **Yes** - with backoff  |
| `502`  | Temporary gateway/upstream issue                                                   | Retry with backoff; check the [status page](https://status.chargeflow.io) | **Yes** - with backoff  |

GET requests are always safe to repeat. Before retrying a write (POST) request, check whether it succeeded; full idempotency semantics are not published yet - see [Idempotency](https://docs.chargeflow.io/docs/reference/api-fundamentals/idempotency) for status.

## Request IDs [#request-ids]

Every API response includes a `requestId` field with a unique identifier for that request. Include this `requestId` when contacting Chargeflow support; it allows the team to locate and diagnose your request quickly.

## HTTP error codes [#http-error-codes]

<Accordions type="single">
  <Accordion title="400 Bad Request">
    The server cannot process your request due to a client-side error. This is typically caused by malformed JSON, incorrect parameter types, or missing required fields.

    **What to do:** Review your request body and query parameters for syntax errors or missing data.
  </Accordion>

  <Accordion title="401 Unauthorized">
    Your request requires authentication but the credentials could not be validated. This occurs when the `x-api-key` header is missing, the key is invalid, or the HMAC signature (if enabled) is incorrect.

    A body of `{"message": "Missing API Key header x-api-key"}` means the header never arrived. Nothing has changed on your account: check that the header is spelled exactly `x-api-key`, that your HTTP client or proxy is not stripping it, and that it carries the API **Access** Key, not the Secret Key.

    **What to do:** Verify your API key and, if HMAC is enabled, ensure your signature is being computed correctly. See [Authentication](./authentication) for full details.
  </Accordion>

  <Accordion title="403 Forbidden">
    The server understood your request but refuses to authorize it. This typically means either insufficient permissions for the requested resource, or an invalid API key.

    A `403` is not a sign that API access has to be switched on for your account: there is no such toggle. If **every** endpoint returns `403`, including the health check, treat it as a malformed request rather than an account problem. The usual cause is an HMAC signature or header that does not match the required format exactly, so re-check the `METHOD\nPATH\nBODY` signing string (raw body, byte for byte, path including the version prefix) against [Authentication](./authentication).

    If the response body is exactly:

    ```json title="Response"
    {
      "message": "Forbidden"
    }
    ```

    your API key is either invalid or lacks permission for this resource. To tell which:

    <Steps>
      <Step>
        Call the health check endpoint: `GET /public/2025-04-01/health-check`. A `200` response means the key itself is valid, so the `403` is a permissions issue: contact your account manager to confirm the key is entitled to this resource. A non-`200` response means the key is invalid.
      </Step>

      <Step>
        If the key is invalid, log in to the Chargeflow app, navigate to **Settings → Developers**, click **Revoke**, then **Generate Keys** to create a new key pair.
      </Step>
    </Steps>

    <Callout title="403 on Alerts or Disputes only">
      Read access to the Alerts and Disputes endpoints is enabled per account and is not granted
      automatically when webhook delivery already works. If the health check returns `200` but `GET
        /alerts` or `GET /disputes` returns `403`, ask [Support](https://docs.chargeflow.io/docs/reference/support) to enable read
      access on your account.
    </Callout>
  </Accordion>

  <Accordion title="404 Not Found">
    The server could not find the resource at the requested URL. This can be caused by a misspelled URL, a deleted resource, or an incorrect ID.

    **What to do:** Verify the URL is correct and that the resource exists in your account.
  </Accordion>

  <Accordion title="429 Too Many Requests">
    You have exceeded the API rate limit by sending too many requests in a short period.

    **What to do:** Implement exponential backoff and retry logic. Reduce the frequency of requests where possible.

    <Callout type="idea" title="Tip">
      If the response carries a `Retry-After` header, it indicates how many seconds to wait before
      retrying; otherwise use exponential backoff. See [Rate Limits](./rate-limits) for more
      information.
    </Callout>
  </Accordion>

  <Accordion title="500 Internal Server Error">
    The server encountered an unexpected error and could not fulfill your request. This may be caused by a programming error, configuration issue, resource exhaustion, or database problem on Chargeflow's side.

    **What to do:** Retry the request after a short delay. If the error persists, contact Chargeflow support with your `requestId`.
  </Accordion>

  <Accordion title="502 Bad Gateway">
    A server acting as a gateway or proxy received an invalid response from an upstream server. This typically indicates a temporary infrastructure issue such as a network timeout or upstream service failure.

    **What to do:** Retry the request. If the error persists, check the [Chargeflow status page](https://status.chargeflow.io) or contact support.
  </Accordion>
</Accordions>

## Retry with backoff [#retry-with-backoff]

Paste-ready retry helpers for the retry-safe statuses (`429`, `500`, `502`). Both honor `Retry-After` when present and fall back to exponential backoff with jitter.

<Tabs items="['Node', 'Python']">
  <Tab value="Node">
    ```typescript title="retry.ts"
    const RETRYABLE = new Set([429, 500, 502]);

    async function fetchWithRetry(url: string, init: RequestInit = {}, maxAttempts = 5) {
      for (let attempt = 1; ; attempt++) {
        const res = await fetch(url, init);
        if (!RETRYABLE.has(res.status) || attempt === maxAttempts) return res;

        const retryAfter = Number(res.headers.get('retry-after'));
        const backoff = 2 ** attempt * 500 + Math.random() * 500; // exponential + jitter
        const waitMs = retryAfter > 0 ? retryAfter * 1000 : backoff;
        await new Promise((r) => setTimeout(r, waitMs));
      }
    }
    ```
  </Tab>

  <Tab value="Python">
    ```python title="retry.py"
    import random, time, requests

    RETRYABLE = {429, 500, 502}

    def request_with_retry(method, url, max_attempts=5, **kwargs):
        for attempt in range(1, max_attempts + 1):
            res = requests.request(method, url, **kwargs)
            if res.status_code not in RETRYABLE or attempt == max_attempts:
                return res

            retry_after = res.headers.get("Retry-After")
            backoff = 2 ** attempt * 0.5 + random.random() * 0.5  # exponential + jitter
            time.sleep(float(retry_after) if retry_after else backoff)
    ```
  </Tab>
</Tabs>

## Health check endpoint [#health-check-endpoint]

Use the health check endpoint to verify connectivity and confirm your API key is valid at any time:

```bash title="Terminal"
curl -X GET https://api.chargeflow.io/public/2025-04-01/health-check \
  -H "x-api-key: YOUR_API_KEY"
```

A `200` response with `{"status": "ok"}` confirms your key is valid and the API is reachable.

## Next steps [#next-steps]

<Cards>
  <Card title="Error Codes Reference" href="/docs/reference/error-codes">
    A quick lookup table for every status code.
  </Card>

  <Card title="Rate Limits" href="/docs/reference/api-fundamentals/rate-limits">
    Back off correctly when you hit a 429.
  </Card>

  <Card title="Authentication" href="/docs/reference/api-fundamentals/authentication">
    Fix 401 and 403 responses.
  </Card>

  <Card title="Support" href="/docs/reference/support">
    What to include when you contact the team.
  </Card>
</Cards>
