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

# Rate Limits

> 240 requests per minute per workspace, limit headers, and retry guidance.

The API allows **240 requests per minute per workspace** (organization), enforced on every authenticated request. The window is rolling; when you exceed it you receive `rate_limit_exceeded` (429) until the window resets.

## Headers

Every API response carries the current budget:

| Header                  | Meaning                                         |
| :---------------------- | :---------------------------------------------- |
| `X-RateLimit-Limit`     | Quota for the window (`240`)                    |
| `X-RateLimit-Remaining` | Requests left in the current window             |
| `X-RateLimit-Reset`     | Unix timestamp (seconds) when the window resets |

```http theme={null}
X-RateLimit-Limit: 240
X-RateLimit-Remaining: 187
X-RateLimit-Reset: 1758573360
```

```json Response (429) theme={null}
{
  "error": {
    "code": "rate_limit_exceeded",
    "message": "Too many requests. Please slow down and respect rate limits."
  }
}
```

## Handling 429s

Back off exponentially and retry. Respect `X-RateLimit-Reset` when present; otherwise start at \~1 second and double each attempt, with jitter.

<CodeGroup>
  ```typescript TypeScript theme={null}
  async function gorkFetch(url: string, init: RequestInit, retries = 4): Promise<Response> {
    const res = await fetch(url, init);
    if (res.status !== 429 || retries === 0) return res;

    const reset = Number(res.headers.get("X-RateLimit-Reset")) * 1000;
    const wait = Number.isFinite(reset) && reset > Date.now()
      ? reset - Date.now()
      : 1000 * 2 ** (4 - retries);
    await new Promise((r) => setTimeout(r, wait + Math.random() * 250));
    return gorkFetch(url, init, retries - 1);
  }
  ```

  ```python Python theme={null}
  import random
  import time

  import requests

  def gork_request(method, url, *, headers, **kwargs):
      for attempt in range(5):
          res = requests.request(method, url, headers=headers, **kwargs)
          if res.status_code != 429 or attempt == 4:
              return res
          reset = res.headers.get("X-RateLimit-Reset")
          if reset and reset.isdigit():
              wait = max(0, int(reset) - int(time.time()))
          else:
              wait = 2 ** attempt
          time.sleep(wait + random.uniform(0, 0.25))
      return res
  ```
</CodeGroup>

## Design guidance

* **Cache reads.** Inbox, thread, and domain metadata change rarely — store them instead of re-fetching per send.
* **Use webhooks, not polling.** Subscribe to `email.received` rather than looping `GET /v1/messages`. Polling burns budget on empty responses; a webhook costs you zero API calls.
* **Batch deliberately.** The deliveries log (`GET /v1/webhooks/deliveries`) and list endpoints cap at 100 rows per page — paginate instead of hammering with tight loops.
* **Separate keys per workload.** The limit is per workspace, not per key — one runaway poller starves your sending path. Isolate bulk reads from latency-sensitive sends.
