# KDPBot API reference

Human- and agent-readable reference for the KDPBot AI REST API. The machine-readable source of truth is the OpenAPI 3.1 document: [https://kdpbot.com/openapi.json](https://kdpbot.com/openapi.json) (also available as [YAML](https://kdpbot.com/openapi.yaml)). Where this page and the spec disagree, the spec wins.

- **Base URL:** `https://kdpbot.com/api/v1` — same-origin and version-pinned. `https://bookgen-backend-5zuf.onrender.com/api/v1` reaches the same backend directly; the unversioned `/api` alias still resolves for existing clients.
- **Auth:** `Authorization: Bearer <access token>` — see [authentication](https://kdpbot.com/docs/authentication.md)
- **Content type:** `application/json`, except the four export actions which return binary file bodies
- Related: [quickstart](https://kdpbot.com/docs/quickstart.md) · [errors](https://kdpbot.com/docs/errors.md) · [rate limits](https://kdpbot.com/docs/rate-limits.md) · [agent instructions](https://kdpbot.com/agents.md)

---

## The KDPBot API surface: REST over HTTPS, and nothing else

KDPBot exposes exactly one API surface: a **REST API over HTTPS** that speaks
JSON, except for the four export actions, which return binary file bodies.

- **There is no GraphQL endpoint.** No `/graphql`, no schema introspection, no
  persisted queries. A probe for one returns `404 NOT_FOUND` because it does not
  exist, not because it is hidden behind authentication.
- **There is no gRPC or SOAP surface, and no WebSocket API.** Long-running work
  is observed by polling `GET /api/books/{id}/generation-status/`, not by
  subscribing to a stream.
- **There is no outbound webhook API.** KDPBot receives a Stripe webhook; it does
  not deliver callbacks to your endpoints.
- **There is an official MCP server**, shipped in the `kdpbot` npm package and
  run with `npx -y kdpbot mcp`. It is a client of this same REST API, not a
  separate surface. See [the MCP server guide](https://kdpbot.com/docs/mcp.md).
- **The machine-readable contract is the OpenAPI 3.1 document** at
  [/openapi.json](https://kdpbot.com/openapi.json)
  ([YAML](https://kdpbot.com/openapi.yaml)) — 67 paths, 75 operations, every one
  carrying an `operationId`, a summary, a description and typed responses. It is
  also listed in the [API catalog](https://kdpbot.com/.well-known/api-catalog).
- **Stability guarantees** — what may change inside v1, what may not, and how a
  sunset is announced — are in the
  [KDPBot API versioning and deprecation policy](https://kdpbot.com/docs/versioning.md).

---

## KDPBot API conventions that will bite you

1. **Trailing slashes are mandatory.** `/api/books` without the slash triggers a redirect that drops the request body.
2. **Almost no pagination.** Every list endpoint returns a bare JSON array with no `{count, next, previous, results}` envelope — except `GET /api/proofreading-reports/{id}/suggestions/`, which is paged 20 at a time (`page`, `page_size`, max 100).
3. **Four action paths use underscores:** `export_pdf`, `export_docx`, `export_epub`, `preview_pdf`. Every other multi-word action uses hyphens.
4. **`POST /api/books/{id}/generate/`** — not `generate_content`.
5. **Token shape differs by endpoint.** Login/register/google return `{"tokens": {...}}`; the refresh endpoint returns flat `{"access", "refresh"}`.
6. **Cross-user ids return `404`, not `403`.** Books and chapters are always scoped to the token holder.
7. **Decimal money fields serialise as strings** (`"19.00"`), not numbers. Credit package prices are the exception and are floats.
8. **All timestamps are ISO 8601.** `UsageQuota.period_start` / `period_end` are plain dates (`YYYY-MM-DD`).

---

## Authentication in the KDPBot API

| Method | Path | Auth | Purpose |
| --- | --- | --- | --- |
| POST | `/api/auth/register/` | — | Create an account. Returns `user` + nested `tokens`. May instead return `email_verification_required: true` with no tokens. |
| POST | `/api/auth/login/` | — | Log in with `password` plus `email` **or** `username`. Returns `user` (incl. nested `subscription`) + `tokens`. |
| POST | `/api/auth/google/` | — | Sign in with a Google OAuth2 `access_token`. Same response shape as login. |
| POST | `/api/auth/token/refresh/` | — | Exchange `refresh` for a new access token. Returns **flat** `{access, refresh}` (rotation is on). |
| POST | `/api/auth/logout/` | — | No-op. The server does not blacklist tokens; discard them client-side. |
| GET | `/api/auth/profile/` | ✔ | Current user incl. `subscription.plan.features`. |
| PATCH | `/api/auth/profile/` | ✔ | Update `first_name`, `last_name`, `email`. |
| POST | `/api/auth/change-password/` | ✔ | `current_password` + `new_password` (min 8). |
| POST | `/api/auth/forgot-password/` | — | Send a reset email for `email`. |
| POST | `/api/auth/reset-password/{uid}/{token}/` | — | Complete the reset with `new_password`. |
| GET | `/api/auth/verify-email/{uid}/{token}/` | — | Confirm an email address from the registration link. |

```jsonc
// POST /api/auth/login/  →  200
{ "user": { "id": 1042, "email": "jane@example.com", "email_verified": true,
            "subscription": { "status": "active",
                              "plan": { "slug": "professional",
                                        "features": { "books_per_month": 30,
                                                      "max_word_count_per_book": 100000,
                                                      "export_formats": ["pdf","docx","epub"],
                                                      "watermark": false } } } },
  "tokens": { "access": "eyJ…", "refresh": "eyJ…" } }
```

---

## Books

`Book` is the root object. Everything else hangs off it.

| Method | Path | Auth | Purpose |
| --- | --- | --- | --- |
| GET | `/api/books/` | ✔ | List your books, newest-updated first. Light serializer with `chapters_count` and `word_count`. Bare array. |
| POST | `/api/books/` | ✔ | Create a book. `201` with the full object. Quota is checked here, consumed on generation completion. |
| GET | `/api/books/{id}/` | ✔ | Full book **including every chapter body**, `progress`, `images`, cover URLs. |
| PATCH | `/api/books/{id}/` | ✔ | Partial update. Changing `target_word_count` or `chapter_count` recalculates each chapter's `target_word_count`. |
| PUT | `/api/books/{id}/` | ✔ | Full replacement. |
| DELETE | `/api/books/{id}/` | ✔ | `204`. Cascades to chapters, progress and covers. Does **not** refund quota. |
| GET | `/api/books/dropdown/` | ✔ | Ultra-light `[{id, title}]` list for selectors. |
| GET | `/api/books/demo/` | — | The public demo book used by the marketing site. |
| POST | `/api/books/{id}/duplicate/` | ✔ | Deep-copy the book and its chapters into a new draft. `201`. |

### Create request

```jsonc
// POST /api/books/
{
  "title": "The Complete Guide to Remote Work",   // required, ≤255
  "subtitle": "Strategies for Distributed Teams", // optional, ≤255
  "genre": "non-fiction",                         // default "fiction"
  "target_audience": "Team leads managing distributed engineering teams",
  "description": "A practical handbook covering hiring, async communication and culture.",
  "chapter_count": 12,                            // default 10
  "target_word_count": 36000,                     // default 40000
  "trim_size": "6x9",                             // default "6x9"
  "content_language": "en",                       // default "en"
  "writing_style": "Direct, example-led, second person",
  "is_autopilot": false
}
```

**Enums.** `genre` ∈ `fiction, non-fiction, mystery, romance, sci-fi, fantasy, biography, self-help, business, other`. `status` ∈ `draft, outline, generating, completed, published`. `trim_size` ∈ `5x8, 5.25x8, 5.5x8.5, 6x9, 6.14x9.21, 6.69x9.61, 7x10, 7.5x9.25, 8x10, 8.25x6, 8.25x8.25, 8.5x11`. `content_language` ∈ `en, es, de, fr, it, pt, ru, uk, pl, zh, ja, ko, ar, hi, nl, sv, tr, id`.

Unknown keys are accepted and silently discarded — a mistyped field name fails quietly, so verify the response reflects what you sent.

### Book response (abridged)

```jsonc
// GET /api/books/{id}/  →  200
{
  "id": 4821,
  "user": { "id": 1042, "username": "jane_publisher", "email": "jane@example.com" },
  "title": "The Complete Guide to Remote Work",
  "subtitle": "Strategies for Distributed Teams",
  "genre": "non-fiction", "target_audience": "…", "description": "…",
  "chapter_count": 12, "target_word_count": 36000,
  "word_count": 36414,                 // computed sum of chapters
  "trim_size": "6x9", "status": "completed",
  "cover_image": "https://…/covers/4821.png",
  "cover_url": "https://…/covers/4821.png",
  "cover_image_url": "https://…/covers/4821.png",
  "writing_style": "…", "tone": "…", "content_language": "en",
  "is_autopilot": false, "autopilot_id": null,
  "chapters": [ /* full Chapter objects incl. content */ ],
  "progress": { "total_chapters": 12, "completed_chapters": 12, "percentage": 100,
                "generation_status": "completed", "generation_time_formatted": "32m 41s" },
  "images": [ { "id": 5511, "url": "https://…", "chapter_number": 3, "type": "chapter", "prompt": "…" } ],
  "is_exported": true, "exported_at": "2026-09-02T14:41:02Z", "export_formats": ["pdf"],
  "created_at": "2026-09-02T13:58:04Z", "updated_at": "2026-09-02T14:41:02Z"
}
```

---

## Generation

The core pipeline. See the [quickstart](https://kdpbot.com/docs/quickstart.md) for a full worked run.

| Method | Path | Auth | Purpose |
| --- | --- | --- | --- |
| POST | `/api/books/{id}/generate-outline/` | ✔ | **Synchronous** (10–60s). Deletes all existing chapters and writes a fresh set from the AI outline. Sets `status: "outline"`. Body ignored. |
| POST | `/api/books/{id}/update-outline/` | ✔ | Persist edits to the outline. Body: `{"chapters":[{chapter_number,title,summary},…]}`. |
| POST | `/api/books/{id}/generate/` | ✔ | **Asynchronous.** `202` + `task_id`. Starts writing every chapter. Sets `status: "generating"`. |
| GET | `/api/books/{id}/generation-status/` | ✔ | Poll progress. `404 {"message":"No generation in progress"}` when the book has never been generated. |
| POST | `/api/books/{id}/pause-generation/` | ✔ | Pause a running job. |
| POST | `/api/books/{id}/resume-generation/` | ✔ | Resume a paused job. |
| POST | `/api/books/{id}/stop-generation/` | ✔ | Stop a running job. |
| POST | `/api/books/{id}/regenerate-chapters/` | ✔ | Re-run generation for chapters that need it. |
| POST | `/api/books/{id}/reorder-chapters/` | ✔ | Reorder chapters. |

```jsonc
// POST /api/books/{id}/generate/  →  202
{ "message": "Generation started", "book_id": 4821,
  "task_id": "b0c1f2e4-6a19-4d3c-9f77-2e5a1c8d0b44", "mode": "celery" }
```

```jsonc
// GET /api/books/{id}/generation-status/  →  200
{ "book": 4821, "total_chapters": 12, "completed_chapters": 5, "current_chapter": 6,
  "percentage": 41, "generation_status": "running",       // running|paused|stopped|completed|error
  "status_message": "Generating chapter 6 of 12",
  "generation_started_at": "2026-09-02T14:02:11Z", "generation_completed_at": null,
  "generation_time_seconds": null, "generation_time_formatted": null,
  "outline": [ { "chapter_number": 1, "title": "Why Most Remote Transitions Fail",
                 "word_count": 3012, "has_content": true } ],
  "progress_percentage": 41, "current_step": "chapter_generation" }
```

**Poll every 15–30 seconds.** A 36,000-word book typically completes in 15–40 minutes. `POST /generate/` is not idempotent — before retrying it after a network failure, poll the status endpoint to see whether the first call started.

Failure modes on `POST /generate/`: `402 PLAN_UPGRADE_REQUIRED` (word count above plan limit), `429 QUOTA_EXCEEDED` (monthly books used up), `400 GENERATION_IN_PROGRESS` (generation already running — the backend returns 400, not 409), `400 VALIDATION_ERROR` (no chapters — call `generate-outline/` first).

---

## Chapters

| Method | Path | Auth | Purpose |
| --- | --- | --- | --- |
| GET | `/api/chapters/` | ✔ | Every chapter across all your books. **No `?book=` filter exists** — read `book.chapters` from `GET /api/books/{id}/` instead. |
| GET | `/api/chapters/{id}/` | ✔ | One chapter. |
| PATCH | `/api/chapters/{id}/` | ✔ | Save edits. This is the editor's save path. |
| PUT / DELETE | `/api/chapters/{id}/` | ✔ | Full replace / delete. |
| POST | `/api/chapters/{id}/regenerate/` | ✔ | **Synchronous** single-chapter rewrite. Returns the new chapter. |
| POST | `/api/chapters/{id}/generate-image/` | ✔ | AI illustration for this chapter. `402 PLAN_UPGRADE_REQUIRED` on the Free plan. |
| GET | `/api/chapters/{id}/images/` | ✔ | List this chapter's images. |
| POST | `/api/chapters/{id}/upload-image/` | ✔ | Upload your own image. |
| DELETE | `/api/chapters/{id}/images/{image_id}/` | ✔ | Remove an image. |

```jsonc
// GET /api/chapters/{id}/  →  200
{ "id": 90311, "book": 4821, "chapter_number": 1,
  "title": "Why Most Remote Transitions Fail",
  "title_options": ["Why Most Remote Transitions Fail", "The Remote Failure Pattern"],
  "summary": "Diagnoses the four structural mistakes companies make in the first ninety days.",
  "content": "<p>…</p>",
  "ai_summary": "…",                    // read-only
  "target_word_count": 3000,
  "actual_word_count": 3012,            // read-only, recomputed server-side
  "word_count": 3012,                   // read-only alias
  "is_generated": true,
  "created_at": "2026-09-02T13:59:20Z", "updated_at": "2026-09-02T14:07:55Z" }
```

`actual_word_count`, `word_count` and `ai_summary` are read-only; values you send for them are ignored. Word count is recalculated from `content` on save.

---

## Covers and export

| Method | Path | Auth | Purpose |
| --- | --- | --- | --- |
| POST | `/api/books/{id}/generate-cover/` | ✔ | Generate cover variants. Returns `variant_1` / `variant_2` objects (each with `image_url` and `prompt`) plus `selected`. Laid out for the book's `trim_size`. |
| POST | `/api/books/{id}/select-cover/` | ✔ | Promote one variant to the book's primary cover. |
| GET | `/api/books/{id}/preview-data/` | ✔ | Aggregated page/cover/trim data for the flip-book preview UI. |
| POST | `/api/books/{id}/export_pdf/` | ✔ | **Binary** `application/pdf`. Free plan adds a watermark. |
| POST | `/api/books/{id}/export_docx/` | ✔ | **Binary** DOCX. Plan-gated: `402` with `allowed_formats`. |
| POST | `/api/books/{id}/export_epub/` | ✔ | **Binary** EPUB. Plan-gated the same way. |
| POST | `/api/books/{id}/preview_pdf/` | ✔ | **Binary** preview PDF. |

The four export actions take an optional `{"settings": {...}}` body and return raw file bytes with `Content-Disposition: attachment` — **not JSON**. Write them straight to a file (`curl -o`). Note the underscores in all four paths.

```bash
curl -sS -X POST "https://bookgen-backend-5zuf.onrender.com/api/books/4821/export_pdf/" \
  -H "Authorization: Bearer $ACCESS" -H 'Content-Type: application/json' \
  -d '{"settings":{"pageSize":"6x9"}}' -o book.pdf
```

---

## Subscriptions, credits and add-ons

Plan features decide `max_word_count_per_book`, `books_per_month`, `ai_images_per_month`, which `export_formats` are allowed, and whether PDFs are watermarked. Check them before you start work.

| Method | Path | Auth | Purpose |
| --- | --- | --- | --- |
| GET | `/api/auth/subscriptions/plans/` | — | All active plans. Bare array. Prices are decimal **strings**. |
| GET | `/api/auth/subscriptions/current/` | ✔ | Current subscription + this period's usage + the plan's `limits`. `404` if the user has no subscription row. |
| GET | `/api/auth/subscriptions/usage/` | ✔ | `{current, history}` — the current quota period plus up to 6 previous ones. |
| GET | `/api/auth/subscriptions/book-creation-options/` | ✔ | **Call this before creating a book.** Whether a book can be started and from which balance. |
| POST | `/api/auth/subscriptions/checkout/` | ✔ | Stripe Checkout session for a plan (`plan` slug, `billing_period`). May instead return `upgraded: true` for an instant prorated upgrade. |
| POST | `/api/auth/subscriptions/portal/` | ✔ | Stripe Customer Portal `url`. |
| GET | `/api/auth/subscriptions/payments/` | ✔ | Last 20 payments with `invoice_pdf_url`. |
| GET | `/api/auth/credits/packages/` | — | Pay-as-you-go credit packages. Prices here are floats. |
| GET | `/api/auth/credits/balance/` | ✔ | `{balance, total_purchased, total_used}`. |
| GET | `/api/auth/credits/transactions/` | ✔ | Credit ledger, newest first. `?limit=` (default 20, max 100). |
| POST | `/api/auth/credits/checkout/` | ✔ | Buy a package (`package_slug`, optional `success_url` / `cancel_url`). |
| POST | `/api/auth/credits/guest-checkout/` | — | Buy credits without an account (`package_slug`, `email`). |
| GET | `/api/auth/addons/available/` | — | Active add-ons (e.g. autopilot, children's book). |
| GET | `/api/auth/addons/my_addons/` | ✔ | Your active add-ons. **Underscore in the path.** |
| POST | `/api/auth/addons/checkout/` | ✔ | Buy an add-on (`addon` slug, `billing_type`). |

```jsonc
// GET /api/auth/subscriptions/book-creation-options/  →  200
{ "can_create": true, "suggested_source": "subscription",
  "options": [{"source": "subscription", "label": "Subscription", "description": "27 books left this period",
               "price": null, "max_words": 150000, "available": true}],
  "suggested_source": "subscription", "max_words": 100000 }
```

```jsonc
// GET /api/auth/subscriptions/current/  →  200
{ "subscription": { "id": 77, "plan_name": "Professional", "status": "active",
                    "current_period_end": "2026-10-02T00:00:00Z",
                    "cancel_at_period_end": false, "is_paid": true, "is_free": false,
                    "plan": { /* full plan incl. features */ } },
  "usage": { "period_start": "2026-09-01", "period_end": "2026-09-30",
             "books_created": 3, "ai_images_used": 14, "ai_edits_used": 2,
             "autopilot_books_used": 0, "proofreading_checks_used": 1,
             "proofreading_edits_used": 0 },
  "limits": { "books_per_month": 30, "max_word_count_per_book": 100000,
              "export_formats": ["pdf","docx","epub"], "watermark": false } }
```

**Quota semantics.** Book quota is consumed when generation **completes**, never at create time and never refunded on delete. Creating and discarding drafts is free.

---

## Stats and public endpoints

| Method | Path | Auth | Purpose |
| --- | --- | --- | --- |
| GET | `/api/stats/` | ✔ | Dashboard counters: `total_books`, `completed_books`, `in_progress`, `draft_books`, `total_words`, `recent_activity[]`. Note: no `/books/` prefix. |
| GET | `/api/public-settings/` | — | Public landing-page configuration (hero animation toggles). |
| POST | `/api/chatbot/ask/` | — | Public support chatbot over the KDPBot knowledge base. Body `{message, session_id?}` → `{answer, session_id}`. |
| GET | `/api/chatbot/health/` | — | Chatbot liveness. |
| POST | `/api/auth/stripe/webhook/` | — | Stripe webhook receiver. Signature-verified, not JWT. **Not for external callers** — listed for completeness. |

---

## Additional endpoint families

These exist and are reachable with the same auth scheme. **Proofreading is fully specified** in the [OpenAPI document](https://kdpbot.com/openapi.json) under the `Proofreading` tag. The families below it are listed here for discovery only — they are not in the spec and their request and response shapes are documented nowhere machine-readable, so verify against a live response before depending on them:

- **Proofreading** — `POST /api/books/{id}/proofreading/`, `GET /api/books/{id}/proofreading/latest/`, `GET /api/books/{id}/proofreading-reports/`, `GET /api/proofreading-reports/{id}/`, `GET /api/proofreading-reports/{id}/suggestions/`, `POST /api/proofreading-reports/{id}/preview/`, `POST /api/proofreading-reports/{id}/apply/` and `POST /api/proofreading-suggestions/{id}/apply/`. AI copy-edit passes producing typed, severity-ranked suggestions you can apply back into chapters. Severities are `low | medium | high`; issue types are `consistency | repetition | logic | style | character | plot | timeline | truncated`. The suggestions listing is the one paginated endpoint in this API. Full request and response shapes are in the OpenAPI spec.
- **Autopilot** — `/api/autopilot/…`. End-to-end unattended book production (outline → chapters → cover → images) as a single job. Requires the autopilot add-on.
- **Children's books** — `/api/children-books/…` plus nested `/pages/`. Page-level text and illustration model.
- **Book covers** — `/api/covers/…` for saved cover designs and A/B variants; plus `/api/upload-cover-image/`, `/api/generate-cover-image/`, `/api/enhance-cover-prompt/`, `/api/edit-cover-image/`.
- **AI metadata helpers** — `/api/books/generate-title/`, `/generate-subtitle/`, `/generate-description/`, `/auto-fill-metadata/`, `/auto-fill-from-keywords/`, `/auto-fill-structure/`. Fill wizard fields from a seed idea or keyword set.
- **Manuscript import** — `/api/books/import-manuscript/preview/` and `/confirm/`. Bring an existing manuscript into a Book so you can proofread, cover and export it.
- **Characters** — `/api/books/{id}/characters/…` for fiction continuity.
- **Videos** — `/api/videos/…` for promotional video assets.
- **Research** — `/api/research/…` keyword and market research.
- **Analytics** — `/api/analytics/log/`, `/dashboard/`, `/funnel/`, `/exports/`.
- **Admin, marketing and ads** — `/api/admin/…`, `/api/marketing/…`, `/api/facebook-ads/…`. Internal; not part of the public agent surface.

---

## KDPBot API errors

Every failure uses the same envelope. Branch on `error.code`, never on `error.message`.

```json
{
  "error": {
    "code": "QUOTA_EXCEEDED",
    "message": "You have used all 3 books included in your plan this period.",
    "status": 429,
    "details": { "quota_type": "books", "limit": 3, "used": 3, "remaining": 0 },
    "documentation_url": "https://kdpbot.com/docs/errors.md"
  }
}
```

Codes: `VALIDATION_ERROR` (400), `INVALID_CREDENTIALS` (401), `AUTHENTICATION_REQUIRED` (401), `TOKEN_EXPIRED` (401), `TOKEN_INVALID` (401), `PLAN_UPGRADE_REQUIRED` (402), `PERMISSION_DENIED` (403), `NOT_FOUND` (404), `METHOD_NOT_ALLOWED` (405), `CONFLICT` (409), `QUOTA_EXCEEDED` (429), `RATE_LIMIT_EXCEEDED` (429), `SERVER_ERROR` (500), `UPSTREAM_AI_ERROR` (502). Full table with resolution steps: [https://kdpbot.com/docs/errors.md](https://kdpbot.com/docs/errors.md).

## KDPBot API rate limits

Responses carry `RateLimit-Limit`, `RateLimit-Remaining` and `RateLimit-Reset`; a `429` also carries `Retry-After`. Read them and self-throttle. Details: [https://kdpbot.com/docs/rate-limits.md](https://kdpbot.com/docs/rate-limits.md).

## 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) · [Rate limits](https://kdpbot.com/docs/rate-limits.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 developer portal](https://kdpbot.com/developers/)
- [KDPBot OpenAPI specification (JSON)](https://kdpbot.com/openapi.json) · [OpenAPI 3.1 (YAML)](https://kdpbot.com/openapi.yaml) · [API catalog](https://kdpbot.com/.well-known/api-catalog)
- [Pricing](https://kdpbot.com/pricing) · [Credits](https://kdpbot.com/credits) · [Terms](https://kdpbot.com/terms) · [Privacy](https://kdpbot.com/privacy) · [Refunds](https://kdpbot.com/refund)
- Support: support@kdpbot.com · [Telegram](https://t.me/blabla_ecommerce) · [WhatsApp](https://wa.me/380994019521) · [Contact](https://kdpbot.com/contacts)
