# KDPBot API rate limits

The KDPBot AI API advertises its limits on every response using RFC-style `RateLimit-*` headers. Read them and self-throttle; do not hard-code numbers and do not wait for a `429` to tell you to slow down.

- **API base URL:** `https://bookgen-backend-5zuf.onrender.com/api`
- Related: [quickstart](https://kdpbot.com/docs/quickstart.md) · [authentication](https://kdpbot.com/docs/authentication.md) · [errors](https://kdpbot.com/docs/errors.md) · [API reference](https://kdpbot.com/docs/api.md) · [OpenAPI spec](https://kdpbot.com/openapi.json)

---

## KDPBot API response headers

Every API response — `2xx` and `4xx` alike — carries these three headers:

| Header | Type | Meaning |
| --- | --- | --- |
| `RateLimit-Limit` | integer | The total number of requests permitted in the current window for this caller and this class of endpoint. |
| `RateLimit-Remaining` | integer | Requests still available in the current window. Decrements on every counted request, including ones that end in a `4xx`. |
| `RateLimit-Reset` | integer | **Seconds remaining** until the current window resets and `RateLimit-Remaining` returns to `RateLimit-Limit`. This is a delta in seconds, not a Unix timestamp. |

A fourth header appears only on `429` responses:

| Header | Type | Meaning |
| --- | --- | --- |
| `Retry-After` | integer | Seconds to wait before the next request will be accepted. **Authoritative** — always prefer it over your own backoff calculation. |

Example on a successful call:

```http
HTTP/1.1 200 OK
Content-Type: application/json
RateLimit-Limit: 120
RateLimit-Remaining: 117
RateLimit-Reset: 47
```

Read as: 120 requests are allowed in this window, 117 remain, and the window resets in 47 seconds.

## How limits are scoped

- **Authenticated requests** are counted per user account, identified from the bearer token — not per IP. Two agents sharing one account share one budget.
- **Unauthenticated requests** (`/api/auth/login/`, `/api/auth/register/`, `/api/books/demo/`, `/api/public-settings/`, `/api/chatbot/ask/`) are counted per client IP and are meaningfully tighter than authenticated limits.
- **Endpoint classes have different budgets.** Cheap reads (`GET /api/books/`, `GET /api/books/{id}/generation-status/`, `GET /api/auth/profile/`) get a generous budget. Expensive work — `POST /api/books/{id}/generate/`, `/generate-outline/`, `/generate-cover/`, `/api/chapters/{id}/regenerate/`, `/api/chapters/{id}/generate-image/`, the proofreading endpoints and the four export actions — is metered far more tightly, because each call costs real model or render time.

Because the budgets differ per class, `RateLimit-Limit` on a `GET /api/books/` response tells you nothing about your remaining budget for `POST /api/books/{id}/generate/`. Track the headers per endpoint class, not globally.

## KDPBot rate limits vs. plan quotas

These are two independent mechanisms that both surface as HTTP `429`. Distinguish them with `error.code`.

| | `RATE_LIMIT_EXCEEDED` | `QUOTA_EXCEEDED` |
| --- | --- | --- |
| What it measures | Request frequency | Plan allowance consumed this billing period |
| Window | Seconds to minutes | The billing month |
| Clears by waiting | Yes — `Retry-After` seconds | No, not within a session |
| Right response | Sleep, then retry | Stop; upgrade at [pricing](https://kdpbot.com/pricing) or buy [credits](https://kdpbot.com/credits) |
| Header to read | `Retry-After` | `error.details.limit` / `used` / `remaining` |

Retrying a `QUOTA_EXCEEDED` response in a loop will never succeed. Always branch on `error.code`, never on the status code alone. See [errors](https://kdpbot.com/docs/errors.md).

## A 429 in full

```http
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
RateLimit-Limit: 20
RateLimit-Remaining: 0
RateLimit-Reset: 38
Retry-After: 38
```

```json
{
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Too many requests. Retry in 38 seconds.",
    "status": 429,
    "details": {
      "limit": 20,
      "remaining": 0,
      "reset_seconds": 38,
      "scope": "generation"
    },
    "documentation_url": "https://kdpbot.com/docs/errors.md"
  }
}
```

Correct handling: sleep 38 seconds, retry the request once, and then continue at a **lower** rate than the one that triggered the limit. Resuming at the original rate simply re-triggers it.

## Self-throttling guidance

**1. Throttle before you are throttled.** Check `RateLimit-Remaining` on every response. When it drops below roughly 20% of `RateLimit-Limit`, start spacing requests across the remainder of `RateLimit-Reset`:

```
delay_seconds = RateLimit-Reset / max(RateLimit-Remaining, 1)
```

This lands you exactly at the window boundary instead of hitting the wall early.

**2. Honour `Retry-After` literally.** It is the server telling you when it will accept traffic again. Do not shorten it, do not add jitter that undercuts it, and do not retry ahead of it "just to check".

**3. Exponential backoff for everything else.** For `502 UPSTREAM_AI_ERROR` and `500 SERVER_ERROR`, back off exponentially with jitter, capped at three attempts:

```
attempt 1 → wait 2s  ± jitter
attempt 2 → wait 4s  ± jitter
attempt 3 → wait 8s  ± jitter
then stop and surface the failure
```

When a `429` carries `Retry-After`, that value **replaces** the computed backoff for that attempt.

**4. Poll generation status every 15–30 seconds, never faster.** Book generation runs for 15–40 minutes. Polling `GET /api/books/{id}/generation-status/` at 1 Hz produces roughly 2,000 pointless requests per book and will exhaust the read budget. Thirty seconds is the recommended interval; below fifteen seconds is abuse.

**5. Serialise expensive calls.** Do not start several book generations concurrently on one account. One `POST /api/books/{id}/generate/` at a time per account is the safe pattern; the generation queue is per-user anyway, so concurrency buys nothing but `429`s and `CONFLICT`s.

**6. Never retry a 4xx unchanged.** `VALIDATION_ERROR`, `NOT_FOUND`, `METHOD_NOT_ALLOWED`, `PERMISSION_DENIED` and `PLAN_UPGRADE_REQUIRED` describe a defect in the request. Retrying burns rate-limit budget and changes nothing.

**7. Batch reads.** `GET /api/books/{id}/` returns the book **and** every chapter body in one response. Do not walk `/api/chapters/{id}/` one id at a time. `GET /api/books/dropdown/` returns just `id` and `title` when that is all you need.

## Reference implementation

```python
import time, random, requests

API = "https://bookgen-backend-5zuf.onrender.com/api"

def call(method, path, token, **kw):
    """One request with rate-limit-aware retry. path must end in '/'."""
    url = f"{API}{path}"
    headers = {"Authorization": f"Bearer {token}", **kw.pop("headers", {})}

    for attempt in range(3):
        r = requests.request(method, url, headers=headers, **kw)

        if r.status_code == 429:
            body = r.json().get("error", {})
            if body.get("code") == "QUOTA_EXCEEDED":
                raise RuntimeError(f"plan quota exhausted: {body.get('message')}")
            wait = int(r.headers.get("Retry-After", 60))
            time.sleep(wait)
            continue

        if r.status_code in (500, 502):
            time.sleep(2 ** (attempt + 1) + random.uniform(0, 1))
            continue

        # Proactive self-throttle before the next call.
        remaining = int(r.headers.get("RateLimit-Remaining", 999))
        limit     = int(r.headers.get("RateLimit-Limit", 999))
        reset     = int(r.headers.get("RateLimit-Reset", 0))
        if limit and remaining < limit * 0.2:
            time.sleep(reset / max(remaining, 1))

        return r

    raise RuntimeError(f"{method} {path} failed after 3 attempts")
```

## More KDPBot AI documentation

- [Quickstart](https://kdpbot.com/docs/quickstart.md) · [Authentication](https://kdpbot.com/docs/authentication.md) · [Errors](https://kdpbot.com/docs/errors.md) · [API reference](https://kdpbot.com/docs/api.md)
- [MCP and agent integration](https://kdpbot.com/docs/mcp.md) · [Agent instructions](https://kdpbot.com/agents.md) · [llms.txt](https://kdpbot.com/llms.txt)
- [KDPBot OpenAPI specification (JSON)](https://kdpbot.com/openapi.json) · [OpenAPI 3.1 (YAML)](https://kdpbot.com/openapi.yaml) · [KDPBot developer portal](https://kdpbot.com/developers/) · [API catalog](https://kdpbot.com/.well-known/api-catalog)
- Plan allowances: [https://kdpbot.com/pricing](https://kdpbot.com/pricing) · Pay-as-you-go: [https://kdpbot.com/credits](https://kdpbot.com/credits)
- Support: support@kdpbot.com · [Telegram](https://t.me/blabla_ecommerce) · [WhatsApp](https://wa.me/380994019521) · [Contact](https://kdpbot.com/contacts)
