# KDPBot API authentication

KDPBot AI uses **JWT bearer tokens** (`djangorestframework-simplejwt`, HS256). There are no API keys, no OAuth client credentials flow, and no session cookies for API callers.

- **API base URL:** `https://bookgen-backend-5zuf.onrender.com/api`
- **Header:** `Authorization: Bearer <access token>`
- **Access token lifetime:** 24 hours
- **Refresh token lifetime:** 30 days
- **Refresh rotation:** enabled — every refresh returns a **new** refresh token
- **Trailing slashes are mandatory on every path.**

Related: [quickstart](https://kdpbot.com/docs/quickstart.md) · [errors](https://kdpbot.com/docs/errors.md) · [rate limits](https://kdpbot.com/docs/rate-limits.md) · [API reference](https://kdpbot.com/docs/api.md) · [OpenAPI spec](https://kdpbot.com/openapi.json)

---

## The header

Send the access token on every authenticated request:

```http
GET /api/books/ HTTP/1.1
Host: bookgen-backend-5zuf.onrender.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzcwMDQ4MDAwLCJ1c2VyX2lkIjoxMDQyfQ.…
Accept: application/json
```

The scheme keyword is exactly `Bearer`. `Token`, `JWT` and `bearer` are not accepted.

## Getting a KDPBot API token pair

Three endpoints issue tokens. All three return the pair **nested** under a `tokens` object.

| Endpoint | Method | Auth | Body |
| --- | --- | --- | --- |
| `/api/auth/register/` | POST | none | `username`, `email`, `password`, optional `first_name`, `last_name` |
| `/api/auth/login/` | POST | none | `password` plus **either** `email` **or** `username` |
| `/api/auth/google/` | POST | none | `access_token` (a Google OAuth2 access token) |

### Log in

```bash
curl -sS -X POST https://bookgen-backend-5zuf.onrender.com/api/auth/login/ \
  -H 'Content-Type: application/json' \
  -d '{"email":"jane@example.com","password":"a-strong-password"}'
```

**200 OK**

```json
{
  "user": {
    "id": 1042,
    "username": "jane_publisher",
    "email": "jane@example.com",
    "first_name": "Jane",
    "last_name": "Doe",
    "email_verified": true,
    "avatar_url": null,
    "is_staff": false,
    "is_superuser": false,
    "subscription": {
      "id": 77,
      "status": "active",
      "plan": {
        "id": 2,
        "name": "Professional",
        "slug": "professional",
        "features": { "books_per_month": 30, "max_word_count_per_book": 100000,
                      "export_formats": ["pdf", "docx", "epub"], "watermark": false }
      },
      "current_period_end": "2026-10-02T00:00:00Z"
    }
  },
  "tokens": {
    "access":  "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9…",
    "refresh": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9…"
  }
}
```

`user.subscription.plan.features` is worth reading and caching: it tells you the caller's `max_word_count_per_book`, which `export_formats` are permitted, and whether exports are watermarked. Checking it up front avoids `402` responses later.

### Register

```bash
curl -sS -X POST https://bookgen-backend-5zuf.onrender.com/api/auth/register/ \
  -H 'Content-Type: application/json' \
  -d '{"username":"jane_publisher","email":"jane@example.com","password":"a-strong-password"}'
```

**201 Created** — same `user` + `tokens` shape as login.

If email verification is required for the deployment, the response instead looks like:

```json
{
  "user": { "id": 1042, "username": "jane_publisher", "email": "jane@example.com", "email_verified": false },
  "message": "Please confirm your email address to activate your account.",
  "email_verification_required": true
}
```

There are **no tokens** in that branch. The account cannot call authenticated endpoints until the emailed link (`GET /api/auth/verify-email/{uid}/{token}/`) is followed. An autonomous agent cannot complete this step; hand control back to a human.

Registration also enforces anti-abuse rules: disposable email domains and Gmail dot/plus aliases are rejected with `400`, and repeated signups from the same browser fingerprint or a blacklisted IP are rejected with `403` or `429`.

## Refreshing an access token

```bash
curl -sS -X POST https://bookgen-backend-5zuf.onrender.com/api/auth/token/refresh/ \
  -H 'Content-Type: application/json' \
  -d '{"refresh":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9…"}'
```

**200 OK**

```json
{
  "access":  "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9…",
  "refresh": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9…"
}
```

> **Two different shapes.** `/api/auth/login/`, `/api/auth/register/` and `/api/auth/google/` nest the pair as `{"tokens": {"access", "refresh"}}`. `/api/auth/token/refresh/` returns them **flat** at the top level. Write two parsers, or normalise at the boundary.

**Rotation is on.** Each successful refresh issues a new refresh token. You must persist the returned `refresh` value and use it next time — reusing the old one after rotation is a bug waiting to surface at the 30-day boundary. The previous refresh token is not blacklisted server-side, but treating it as dead is the correct client behaviour.

### Refresh strategy for agents

Refresh **proactively**, not reactively. Decode the `exp` claim (or track issue time) and refresh when the access token is within ~30 minutes of expiry. A book generation poll loop can easily run past the 24-hour boundary; discovering that via a `401` in the middle of a long job is avoidable.

Reactive fallback: on a `401` with code `TOKEN_EXPIRED`, refresh once and replay the original request exactly once. If the replay also fails, stop and surface the failure — do not loop.

## KDPBot token lifetimes at a glance

| Token | Lifetime | Rotated | Notes |
| --- | --- | --- | --- |
| `access` | 24 hours | — | Sent on every request in the `Authorization` header |
| `refresh` | 30 days | Yes, on every refresh | Store securely; it is a 30-day credential |

Algorithm: HS256. Claims include `token_type`, `exp` and `user_id`. Tokens are opaque to clients for authorisation purposes — do not make access decisions from decoded claims; call `GET /api/auth/profile/` instead.

## Which KDPBot API endpoints need auth

Authentication is **opt-in per endpoint**, not global.

**Public (no token needed):** `POST /api/auth/register/`, `POST /api/auth/login/`, `POST /api/auth/google/`, `POST /api/auth/token/refresh/`, `POST /api/auth/forgot-password/`, `POST /api/auth/reset-password/{uid}/{token}/`, `GET /api/auth/verify-email/{uid}/{token}/`, `GET /api/auth/subscriptions/plans/`, `GET /api/auth/addons/available/`, `GET /api/auth/credits/packages/`, `POST /api/auth/credits/guest-checkout/`, `GET /api/books/demo/`, `GET /api/public-settings/`, `POST /api/chatbot/ask/`.

**Authenticated:** everything else — all of `/api/books/`, `/api/chapters/`, `/api/stats/`, and the subscription, credits and add-on endpoints that read or change your own account.

## Ownership scoping

Every book and chapter query is filtered to the authenticated user. Requesting another user's object id returns **`404 NOT_FOUND`, not `403`**. A `404` on an id you are certain exists almost always means you are holding a token for the wrong account.

## What a 401 looks like

```http
HTTP/1.1 401 Unauthorized
Content-Type: application/json
WWW-Authenticate: Bearer realm="api"
```

```json
{
  "error": {
    "code": "TOKEN_EXPIRED",
    "message": "The access token has expired. Refresh it at POST /api/auth/token/refresh/.",
    "status": 401,
    "details": { "token_type": "access" },
    "documentation_url": "https://kdpbot.com/docs/errors.md"
  }
}
```

Four codes arrive with `401`, and they call for different responses:

| Code | Meaning | Correct response |
| --- | --- | --- |
| `AUTHENTICATION_REQUIRED` | No `Authorization` header was sent | Attach the header and retry |
| `TOKEN_EXPIRED` | Access token past its 24-hour life | Refresh once, replay once |
| `TOKEN_INVALID` | Malformed, wrong-typed or unverifiable token | Discard it and log in again; refreshing will not help |
| `INVALID_CREDENTIALS` | Wrong email/username or password at login | Stop. This is a human decision — never retry with guesses |

Full table: [https://kdpbot.com/docs/errors.md](https://kdpbot.com/docs/errors.md).

## Logout

```bash
curl -sS -X POST https://bookgen-backend-5zuf.onrender.com/api/auth/logout/
```

`POST /api/auth/logout/` is a client-side convention only. **The server does not blacklist the refresh token.** Real logout means deleting both tokens from your own storage. Treat a leaked refresh token as valid for its full 30 days and rotate the account password if one escapes.

## Password management

| Endpoint | Method | Auth | Body |
| --- | --- | --- | --- |
| `/api/auth/change-password/` | POST | yes | `current_password`, `new_password` (min 8 chars) |
| `/api/auth/forgot-password/` | POST | no | `email` |
| `/api/auth/reset-password/{uid}/{token}/` | POST | no | `new_password` (min 8 chars) |

`uid` and `token` come from the emailed reset link.

## Storage guidance

- Never embed a KDPBot token in front-end source, a public repository, a URL query string, or a log line.
- Store both tokens in an OS keychain, a secrets manager, or an encrypted store — the refresh token is a 30-day credential.
- One token pair per user account. Do not share a pair between agents that might refresh concurrently; rotation makes concurrent refreshes race.
- If a token leaks, change the account password immediately (there is no server-side revocation endpoint) and contact support@kdpbot.com.

## More KDPBot AI documentation

- [Quickstart](https://kdpbot.com/docs/quickstart.md) · [Errors](https://kdpbot.com/docs/errors.md) · [Rate limits](https://kdpbot.com/docs/rate-limits.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)
- Support: support@kdpbot.com · [Telegram](https://t.me/blabla_ecommerce) · [WhatsApp](https://wa.me/380994019521) · [Contact page](https://kdpbot.com/contacts)
