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

# Best practices

> How to handle rate limit headers and insufficient credit responses when building production clients against the Crustdata API.

Practical patterns for building reliable clients against the Crustdata API. Each section explains the signal the API sends, then shows the code you need to react to it.

## Handle rate limits

Crustdata returns rate limit headers on every response. Read them after each call and pace your client accordingly — this is more reliable than guessing limits up front, and it gives you visibility before you hit the cap.

<Note>
  Rate limits are enforced on a **sliding window**. The window does not
  reset at fixed clock intervals — instead, each request counts against the
  limit for the duration of the window, and `x-ratelimit-reset` tells you
  how many seconds until the oldest request ages out and capacity frees up.
  Spreading traffic evenly is safer than bursting and waiting for a "reset."
</Note>

### Rate limit headers

| Header                  | Meaning                                                        |
| ----------------------- | -------------------------------------------------------------- |
| `x-ratelimit-limit`     | Maximum requests allowed within the sliding window.            |
| `x-ratelimit-remaining` | Requests you have left right now.                              |
| `x-ratelimit-reset`     | Seconds until the next request ages out and capacity frees up. |

Example values on a response:

```
x-ratelimit-limit: 30
x-ratelimit-remaining: 29
x-ratelimit-reset: 60
```

### Read the headers and pace requests

After each request, inspect `x-ratelimit-remaining`. When it gets close to zero, wait `x-ratelimit-reset` seconds before sending the next call.

<CodeGroup>
  ```python Python theme={"theme":"vitesse-black"}
  import time
  import requests

  def call_crustdata(url, payload, api_key):
      response = requests.post(
          url,
          headers={
              "authorization": f"Bearer {api_key}",
              "content-type": "application/json",
              "x-api-version": "2025-11-01",
          },
          json=payload,
      )

      remaining = int(response.headers.get("x-ratelimit-remaining", 1))
      reset = int(response.headers.get("x-ratelimit-reset", 0))

      if remaining <= 1 and reset > 0:
          time.sleep(reset)

      response.raise_for_status()
      return response.json()
  ```

  ```javascript Node.js theme={"theme":"vitesse-black"}
  async function callCrustdata(url, payload, apiKey) {
      const response = await fetch(url, {
          method: "POST",
          headers: {
              authorization: `Bearer ${apiKey}`,
              "content-type": "application/json",
              "x-api-version": "2025-11-01",
          },
          body: JSON.stringify(payload),
      });

      const remaining = Number(
          response.headers.get("x-ratelimit-remaining") ?? 1,
      );
      const reset = Number(response.headers.get("x-ratelimit-reset") ?? 0);

      if (remaining <= 1 && reset > 0) {
          await new Promise((r) => setTimeout(r, reset * 1000));
      }

      if (!response.ok) {
          throw new Error(`Request failed: ${response.status}`);
      }
      return response.json();
  }
  ```
</CodeGroup>

### Retry on 429 with backoff and jitter

If you do receive a `429 Too Many Requests`, wait `x-ratelimit-reset` seconds and retry. Add jitter so concurrent workers do not all retry at the same instant.

<CodeGroup>
  ```python Python theme={"theme":"vitesse-black"}
  import random
  import time
  import requests

  def call_with_retry(url, payload, api_key, max_retries=3):
      headers = {
          "authorization": f"Bearer {api_key}",
          "content-type": "application/json",
          "x-api-version": "2025-11-01",
      }
      for attempt in range(max_retries):
          response = requests.post(url, headers=headers, json=payload)
          if response.status_code != 429:
              response.raise_for_status()
              return response.json()
          reset = int(response.headers.get("x-ratelimit-reset", 1))
          time.sleep(reset + random.uniform(0, 1))
      raise RuntimeError("Exceeded retry budget")
  ```

  ```javascript Node.js theme={"theme":"vitesse-black"}
  async function callWithRetry(url, payload, apiKey, maxRetries = 3) {
      const headers = {
          authorization: `Bearer ${apiKey}`,
          "content-type": "application/json",
          "x-api-version": "2025-11-01",
      };
      for (let attempt = 0; attempt < maxRetries; attempt++) {
          const response = await fetch(url, {
              method: "POST",
              headers,
              body: JSON.stringify(payload),
          });
          if (response.status !== 429) {
              if (!response.ok) {
                  throw new Error(`Request failed: ${response.status}`);
              }
              return response.json();
          }
          const reset = Number(
              response.headers.get("x-ratelimit-reset") ?? 1,
          );
          const jitter = Math.random();
          await new Promise((r) => setTimeout(r, (reset + jitter) * 1000));
      }
      throw new Error("Exceeded retry budget");
  }
  ```
</CodeGroup>

<Note>
  See [Rate limits](/general/rate-limits) for default per-endpoint limits
  and how to request higher throughput.
</Note>

## Handle insufficient credits

When your account has no remaining credits, the API returns `402 Payment Required` with a structured error body. Treat this as terminal — retrying will not succeed until credits are added.

### Response shape

```json theme={"theme":"vitesse-black"}
{
    "error": {
        "type": "insufficient_credits",
        "message": "Insufficient credits. Please get in touch with the Crustdata sales team.",
        "metadata": []
    }
}
```

| Field            | Description                                            |
| ---------------- | ------------------------------------------------------ |
| `error.type`     | `insufficient_credits` for this case.                  |
| `error.message`  | Human-readable message safe to surface in logs and UI. |
| `error.metadata` | Reserved for additional context. May be empty.         |

### Detect and stop

Catch `402` early, pause the workflow, and alert the operator. Do not feed the same request back into a retry loop.

<CodeGroup>
  ```python Python theme={"theme":"vitesse-black"}
  import requests

  class InsufficientCreditsError(Exception):
      pass

  def call_crustdata(url, payload, api_key):
      response = requests.post(
          url,
          headers={
              "authorization": f"Bearer {api_key}",
              "content-type": "application/json",
              "x-api-version": "2025-11-01",
          },
          json=payload,
      )

      if response.status_code == 402:
          body = response.json()
          raise InsufficientCreditsError(body["error"]["message"])

      response.raise_for_status()
      return response.json()
  ```

  ```javascript Node.js theme={"theme":"vitesse-black"}
  class InsufficientCreditsError extends Error {}

  async function callCrustdata(url, payload, apiKey) {
      const response = await fetch(url, {
          method: "POST",
          headers: {
              authorization: `Bearer ${apiKey}`,
              "content-type": "application/json",
              "x-api-version": "2025-11-01",
          },
          body: JSON.stringify(payload),
      });

      if (response.status === 402) {
          const body = await response.json();
          throw new InsufficientCreditsError(body.error.message);
      }

      if (!response.ok) {
          throw new Error(`Request failed: ${response.status}`);
      }
      return response.json();
  }
  ```
</CodeGroup>

<Warning>
  Do not retry `402` responses. Repeated calls without adding credits will
  return the same error and obscure the real problem in your logs.
</Warning>

### Get more credits

Reach out to [support@crustdata.co](mailto:support@crustdata.co) to discuss your usage, upgrade your plan, or add credits to your account.
