---
title: "Developer Docs — KUZOG API, OpenAPI and Agent Access"
description: "KUZOG developer documentation: the contact API, OpenAPI 3.1 spec, JSON error format, rate limits, Markdown content negotiation, llms.txt and sitemap."
canonical: "https://www.kuzog.com/docs/"
last_updated: 2026-08-22
language: en
---

# Developer Docs — KUZOG API, OpenAPI and Agent Access

> KUZOG developer documentation: the contact API, OpenAPI 3.1 spec, JSON error format, rate limits, Markdown content negotiation, llms.txt and sitemap.

Canonical: https://www.kuzog.com/docs/
Language: en

How to read kuzog.com as a machine and how to reach KUZOG through it. One small HTTP API, one OpenAPI description, Markdown on every page, and the agent files that tie them together.

## Overview

kuzog.com is a static site served from Cloudflare Pages, with a handful of Pages Functions behind `/api/`. It is built to be read without a browser:

- Every route is prerendered to HTML at build time, so the full content of a page is in the initial markup. No JavaScript is needed to read it.
- Every page is also available as Markdown: send `Accept: text/markdown` to any page URL, or fetch its `index.md` sibling. See [Markdown content negotiation](https://www.kuzog.com/docs/#markdown).
- [llms.txt](https://www.kuzog.com/llms.txt) is a short brief and [llms-full.txt](https://www.kuzog.com/llms-full.txt) the complete content reference, both generated from the site's own copy.
- [sitemap.xml](https://www.kuzog.com/sitemap.xml) lists every indexable URL; [robots.txt](https://www.kuzog.com/robots.txt) allows search engines, answer engines and AI assistants, and blocks the training-only crawlers CCBot and Bytespider.
- The HTTP API is described by [openapi.json](https://www.kuzog.com/openapi.json) (OpenAPI 3.1) and documented on this page.
- Every page carries schema.org JSON-LD: the organisation, its two ventures, products, FAQs, articles and open roles.

The canonical host is `www.kuzog.com`; the apex `kuzog.com` answers 301 to it. Use the `www` form in every request.

## Quick start

Four requests, no key, no signup — copy, paste, run:

**1. Is the site up?**

```bash
curl -s https://www.kuzog.com/api/health
```

**2. Read a page as Markdown**

```bash
curl -s -H 'Accept: text/markdown' https://www.kuzog.com/hydrobio/
```

**3. List a page of the site index**

```bash
curl -s 'https://www.kuzog.com/api/pages?limit=2'
```

**4. Ask the MCP server what it can do**

```bash
curl -s -X POST https://www.kuzog.com/mcp \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
```

Everything above is read-only and requires nothing to run: no API key, no OAuth, no account. See [Authentication](https://www.kuzog.com/docs/#authentication) for the one exception — sending an enquiry — and [Pricing](https://www.kuzog.com/docs/#pricing) for what, if anything, costs money.

## Authentication

There is none. No API keys exist, no OAuth, no tokens to request. Everything on the site, including the API description, is public and unauthenticated.

The one write operation, `POST /api/contact`, is protected by Cloudflare Turnstile instead. A valid `cf-turnstile-response` token is required on every submission, and a token is issued only when a human completes the challenge in a browser on www.kuzog.com. Tokens are single-use and expire within minutes. There is no server-to-server route around this, by design: the form delivers email to a person, and the challenge is what keeps it usable.

What this means for an agent: you cannot submit the form autonomously. Hand off to a human instead — open [https://www.kuzog.com/contact/](https://www.kuzog.com/contact/) for the user with the message drafted, or tell them to email [management@kuzog.com](mailto:management@kuzog.com). A submission without a token is answered with `missing-captcha`; a spent or expired token with `captcha-failed`.

## Endpoints

Request bodies: the browser form posts `multipart/form-data`; from a function-calling schema post an `application/json` object with the same field names instead. Validation and responses are identical for both.

Three endpoints, all under `https://www.kuzog.com/api/`, all JSON in every response. The full description with schemas is at [https://www.kuzog.com/openapi.json](https://www.kuzog.com/openapi.json).

| Method | Path | operationId | Purpose |
| --- | --- | --- | --- |
| POST | `/api/contact` | `submitContactEnquiry` | Deliver an enquiry to the KUZOG team by email. |
| GET | `/api/health` | `getHealth` | Liveness probe; reports rate-limit standing without spending quota. |
| GET | `/api/pages` | `listPages` | Paginated index of every page, in every language — the same data as `pages.json`. |

`POST /api/contact` takes `multipart/form-data` (URL-encoded works too). Required: `name`, `email`, `message`, `cf-turnstile-response`. Optional: `topic` (shown in the subject line), `industry`, `submitted_at`. `botcheck` is a honeypot and must be empty or absent. Fields are trimmed, stripped of control characters and cut at their maximum length:

| Field | Required | Max length |
| --- | --- | --- |
| `name` | yes | 120 |
| `email` | yes | 200 |
| `message` | yes | 5000 |
| `topic` | no | 80 |
| `industry` | no | 80 |
| `cf-turnstile-response` | yes | Turnstile token |
| `botcheck` | must be empty | 0 |

**Send an enquiry**

```bash
curl -sS -X POST https://www.kuzog.com/api/contact \
  -F 'name=Ada Lovelace' \
  -F 'email=ada@example.com' \
  -F 'topic=Hydrobio' \
  -F 'message=We farm 40 ha of sandy soil and would like to trial Hydrobio on one parcel.' \
  -F 'cf-turnstile-response=<token solved in the browser>'

# 200
{"success":true}
```

A delivered enquiry answers `200 {"success":true}` and sets the sender as reply-to, so the team answers them directly. A filled honeypot also answers 200 and sends nothing; the two are deliberately indistinguishable.

**Check the service**

```bash
curl -sS https://www.kuzog.com/api/health

# 200
{
  "status": "ok",
  "service": "kuzog-site",
  "time": "2026-08-22T10:00:00.000Z",
  "docs": "https://www.kuzog.com/docs/",
  "openapi": "https://www.kuzog.com/openapi.json"
}
```

`/api/health` proves the Functions are deployed and answering. It does not call Turnstile or the email provider, so a 200 there says the route is up, not that a given submission will be delivered. Any other method on any of these three endpoints answers `405` with an `Allow` header; any other path under `/api/` answers `404`. All three are JSON.

`GET /api/pages` accepts `language`, `type`, `limit` and `cursor` query parameters and answers with `{"data": [...], "next_cursor": ...}` — see [Pagination](https://www.kuzog.com/docs/#pagination) for how to page through it.

**List pages**

```bash
curl -sS 'https://www.kuzog.com/api/pages?limit=2'

# 200
{"data":[{"..."}],"next_cursor":"eyJvZmZzZXQiOjJ9"}
```

## Pagination

`GET /api/pages` is the one paginated endpoint. `language` and `type` filter which pages are in the set; `limit` caps how many come back at once; `cursor` — copied from the previous response — asks for the next slice of that same filtered set.

**Follow the cursor**

```bash
curl -sS 'https://www.kuzog.com/api/pages?limit=2&cursor=eyJvZmZzZXQiOjJ9'
```

`next_cursor` is `null` on the last page — that is the end-of-list signal, not a count. The same cursor also rides on the HTTP response as `Link: <.../api/pages?...&cursor=...>; rel="next"`, so a client that reads headers rather than bodies can keep paging without parsing JSON at all; the header is simply absent on the last page.

There is no `total_count` field and no page-number parameter — cursors are opaque and only ever move forward. Treat the cursor string as a token to copy, not as data to parse or construct.

## Idempotency

`POST /api/contact` accepts an `Idempotency-Key` request header. Send the same key on a retry of the same submission and the stored response is replayed instead of a second email going out — the case this exists for is a client that timed out waiting for a reply and cannot tell whether the first attempt was delivered.

**Retry safely**

```bash
curl -sS -X POST https://www.kuzog.com/api/contact \
  -H 'Idempotency-Key: 6c9f2e1a-6b3b-4b8b-9b1a-2a7e9d9c9b10' \
  -F 'name=Ada Lovelace' \
  -F 'email=ada@example.com' \
  -F 'message=We farm 40 ha of sandy soil…' \
  -F 'cf-turnstile-response=<token solved in the browser>'
```

A replayed response carries `Idempotent-Replayed: true`, so a client can tell it received a cached answer rather than a fresh send. Keys are held for 24 hours, best-effort, in the memory of the Cloudflare edge isolate that first saw them — the same caveat [Rate limits](#rate-limits) states for the request counters: a retry served by a different isolate may not recognise the key and will send again.

Reusing a key with a changed request body is refused rather than silently answered with the first body's result: `422 idempotency-key-reused`. A key already being processed by an in-flight request answers `409 idempotency-key-in-flight` — wait a few seconds and retry the same key. Generate a fresh key (a UUID is enough) per distinct enquiry, and reuse one only to retry that exact submission.

## Errors

Every error under `/api/` uses one envelope. `error` is a stable machine code to branch on; codes are added, never renamed. `message` says what went wrong, `hint` what to do about it, `docs` links here, and `status` repeats the HTTP status so a logged body stands on its own. `codes` appears only on `captcha-failed` and carries Turnstile’s own error codes.

**Error envelope**

```json
{
  "success": false,
  "error": "captcha-failed",
  "message": "Cloudflare Turnstile rejected the token.",
  "hint": "Inspect the codes array: timeout-or-duplicate means the token expired or was already used, so solve the challenge again and resubmit.",
  "docs": "https://www.kuzog.com/docs/#errors",
  "status": 400,
  "codes": [
    "timeout-or-duplicate"
  ]
}
```

| Code | Status | Meaning | What to do |
| --- | --- | --- | --- |
| `malformed-request` | 400 | The request body could not be read as form data. | Send a multipart/form-data or application/x-www-form-urlencoded body. |
| `invalid-fields` | 400 | A required field is missing or the email address is not valid. | Provide non-empty name and message fields and a syntactically valid email. |
| `missing-captcha` | 400 | No Turnstile token was included in the request. | A human must solve the Cloudflare Turnstile challenge in a browser; send its token as cf-turnstile-response. |
| `captcha-failed` | 400 | Cloudflare Turnstile rejected the token. | Inspect the codes array: timeout-or-duplicate means the token expired or was already used, so solve the challenge again and resubmit. |
| `captcha-unreachable` | 502 | The Turnstile verification service could not be reached. | This is transient. Retry with a fresh token after a short wait, or email management@kuzog.com. |
| `send-failed` | 502 | The enquiry was validated but the email could not be sent. | Retry later with a fresh Turnstile token, or email management@kuzog.com directly. |
| `rate-limited` | 429 | Too many requests from this address in the current window. | Wait for the number of seconds in the Retry-After header, then try again. |
| `method-not-allowed` | 405 | This endpoint does not support the request method. | Use one of the methods listed in the Allow header. |
| `not-found` | 404 | No API endpoint exists at this path. | See the OpenAPI description at https://www.kuzog.com/openapi.json for the available endpoints. |
| `invalid-parameter` | 400 | A query parameter or request header has a value the endpoint does not accept. | Read the detail field for the parameter at fault, and correct it using the OpenAPI description. |
| `idempotency-key-in-flight` | 409 | A request with this Idempotency-Key is still being processed. | Wait a few seconds, then retry with the same Idempotency-Key to receive the stored response. |
| `idempotency-key-reused` | 422 | This Idempotency-Key was already used with a different request body. | Use a new Idempotency-Key for a different enquiry; reuse a key only to retry the identical request. |
| `index-unavailable` | 503 | The site content index could not be read. | This is transient. Retry after a short wait, or read https://www.kuzog.com/sitemap.xml instead. |

The Turnstile `codes` worth knowing: `timeout-or-duplicate` means the token expired or was already used, so solve the challenge again and resubmit; `invalid-input-response` means the token was never valid. Successful responses are always `{"success":true}` and nothing else.

## Rate limits

One policy, named `contact`: 10 requests per 60 seconds per client IP address on `POST /api/contact`, as a sliding window. Only contact submissions count; `/api/health`, `405` and `404` responses report your standing without spending it.

Every `/api/` response carries the policy and your current standing, in the IETF structured-field form ([draft-ietf-httpapi-ratelimit-headers](https://datatracker.ietf.org/doc/draft-ietf-httpapi-ratelimit-headers/)) and in the legacy `X-RateLimit-*` trio for clients that only know those:

| Header | Example | Meaning |
| --- | --- | --- |
| `RateLimit-Policy` | `"contact";q=10;w=60` | The policy: `q` requests per window of `w` seconds. Constant. |
| `RateLimit` | `"contact";r=9;t=60` | `r` requests remaining, `t` seconds until the oldest counted request leaves the window (0 when none is counted). |
| `X-RateLimit-Limit` | `10` | Requests allowed per window. |
| `X-RateLimit-Remaining` | `9` | Requests remaining in the current window. |
| `X-RateLimit-Reset` | `60` | Seconds until the window resets. A delta, not a Unix timestamp. |
| `Retry-After` | `42` | On a `429` only: seconds to wait. Never 0. |

Over the limit, the request is refused with `429` and the `rate-limited` envelope before anything is read or verified, so it costs neither side anything. Refused requests are not counted, so a client that waits `Retry-After` seconds is let back in as the window moves on.

The count is best-effort: it lives in the memory of the Cloudflare edge isolate that serves the request, and there are many isolates in many locations. Two requests from one address may be counted separately, and the count resets when an isolate is recycled. That is deliberate. Turnstile is the real gate against automation; this limit only stops one address from turning a single isolate into a firehose. Treat the headers as advisory and the `429` as authoritative.

## Asynchronous operations

None. Every request on this site, including `POST /api/contact`, finishes and answers with its final status — `200`, an error code, or `429` — in that same response. Nothing here replies `202 Accepted` with a job to poll, and there is no webhook to register for a later callback.

This is a property of what the API does, not a corner cut: sending an enquiry and reading the page index are both fast enough to finish inline, so there is no work to hand off to a background job in the first place. If a future endpoint needs one, it will say so in the [changelog](#changelog) and carry a new [API-Version](#versioning), rather than silently turning a `200` into a `202` under callers’ feet.

## Versioning and deprecation

The API is date-versioned. Every `/api/` response carries `API-Version: 2026-09-15` — the date of the last change to any request or response shape. Record the value you integrated against; a newer date on a later response means the contract has changed and the [changelog](#changelog) says how.

Breaking changes never replace a shape in place. They ship under a new date, and the previous shape keeps answering for at least 180 days. During that period every response of the old shape carries `Deprecation: @<unix-seconds>` ([RFC 9745](https://www.rfc-editor.org/rfc/rfc9745)) and `Sunset: <HTTP-date>` ([RFC 8594](https://www.rfc-editor.org/rfc/rfc8594)) and a `Link: <https://www.kuzog.com/docs/#versioning>; rel="deprecation"`. Additive changes — a new optional field, a new endpoint, a new error code — do not bump the date and do not deprecate anything.

| Signal | Where | Meaning |
| --- | --- | --- |
| `API-Version` | Every `/api/` response; `info.x-api-version` in `openapi.json` | The contract date the response was produced under. |
| `Deprecation` | Responses of a shape scheduled for removal | When the deprecation was announced. Absent today. |
| `Sunset` | Same responses | When the shape stops answering — at least 180 days after `Deprecation`. Absent today. |
| `Link`; `rel="sunset"` | Every `/api/` response, deprecated or not | Always points at this section, so a client never has to hard-code the policy URL. |
| `Link`; `rel="deprecation"` | Responses of a shape scheduled for removal | Points at the note explaining what replaced it. Present only once a shape is actually deprecated — never sent speculatively. |

You may also send `API-Version: <date>` as a request header to name the contract you integrated against (it is declared as an optional parameter on every operation in `openapi.json`). With one live version it changes nothing today; during a future deprecation window it is how the server knows which shape to answer you with.

Nothing is deprecated today. There is no version segment in the URL path: `/api/contact`, `/api/health` and `/api/pages` are the only paths, and they stay. Deprecation is also unrelated to whether a call finishes inline — see [Asynchronous operations](https://www.kuzog.com/docs/#async-operations) — and to whether a retry is safe to repeat — see [Idempotency](https://www.kuzog.com/docs/#idempotency).

## Markdown content negotiation

Every page URL has two representations, HTML and Markdown, chosen by the `Accept` header. The Markdown is generated at build time alongside the HTML, so the two never drift.

**Ask for Markdown**

```bash
curl -sS -H 'Accept: text/markdown' https://www.kuzog.com/hydrobio/

# 200
# Content-Type: text/markdown; charset=utf-8
# Content-Location: /hydrobio/index.md
# Link: <https://www.kuzog.com/hydrobio/>; rel="alternate"; type="text/html"
# Vary: Accept
```

- The highest `q` wins. On a tie, the type listed first wins; `*/*` and `text/*` alone mean HTML, the default. A missing `Accept` means HTML.
- Every negotiated response carries `Vary: Accept`, so a cache never hands one client’s representation to another.
- HTML responses carry `Link: <…/index.md>; rel="alternate"; type="text/markdown"`, pointing at the Markdown sibling; Markdown responses link back to the HTML.
- If `Accept` rules out both `text/html` and `text/markdown` (for example `Accept: application/json`), the answer is `406 Not Acceptable` with a plain-text body listing the two available representations.
- Only page URLs negotiate: `GET` or `HEAD`, outside `/api/`, with no file extension. `/llms.txt`, `/sitemap.xml`, `/openapi.json` and assets are served as-is.

Prefer a plain URL? Each page has a Markdown sibling at `<page>/index.md`: `https://www.kuzog.com/index.md` for the home page, `https://www.kuzog.com/hydrobio/index.md`, `https://www.kuzog.com/blog/<slug>/index.md`, and so on. Fetch it with no special header.

**Or fetch the sibling directly**

```bash
curl -sS https://www.kuzog.com/microplantes/index.md
```

## MCP server

A Model Context Protocol server at [https://www.kuzog.com/mcp](https://www.kuzog.com/mcp), speaking Streamable HTTP and JSON-RPC 2.0. No authentication. Its manifest is served at both [/.well-known/mcp](https://www.kuzog.com/.well-known/mcp) and `/.well-known/mcp.json` — point your client at whichever it looks for.

Three read-only tools: `list_pages`, `get_page`, `search_site`. Every page is also exposed as an MCP resource carrying its Markdown, for a client that prefers reading resources over calling a tool.

**List the tools over JSON-RPC**

```bash
curl -sS -X POST https://www.kuzog.com/mcp \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
```

The tools are all read-only: none of them can send an enquiry or change anything on the site. To reach a person, still go through `POST /api/contact` — see [Authentication](https://www.kuzog.com/docs/#authentication) — or the [MCP server](https://www.kuzog.com/docs/#mcp-server) again once it grows a write tool, if it ever does.

## WebMCP (in-browser tools)

Separate from the MCP server above: on a browser that implements it, every kuzog.com page registers tools directly with `navigator.modelContext` (renamed `document.modelContext` in Chrome 150 and later) — the W3C Web Machine Learning Community Group’s draft for a page to expose callable tools to whatever agent is driving that browser, with no network hop to a separate server.

- `get_page_markdown` — the current page, or a given same-origin path, as Markdown.
- `list_pages` — `GET /api/pages`.
- `search_site` — the NLWeb `/ask` endpoint.
- `navigate_to` — send the tab to another same-origin page.

All four are read-only in the sense that matters here: none of them submits the contact form or writes anything back to KUZOG. As of this writing WebMCP ships only behind Chrome’s origin trial, so on every other browser these tools simply do not register, and nothing above depends on them — the MCP server, the API and Markdown content negotiation all work exactly the same without a WebMCP-capable browser in the loop.

## NLWeb

`GET https://www.kuzog.com/ask?query=<question>` answers a natural-language question from the site’s own content and names the pages the answer drew on. JSON by default:

**Ask a question**

```bash
curl -sS 'https://www.kuzog.com/ask?query=what+is+hydrobio'
```

Add `streaming=true`, or send `Accept: text/event-stream`, to get the same answer streamed as Server-Sent Events instead of waiting for the whole JSON body:

**Ask and stream the answer**

```bash
curl -sS -N 'https://www.kuzog.com/ask?query=what+is+hydrobio&streaming=true'
```

Like every read on this page, `/ask` needs no key and costs nothing to call.

## Agent files

The files to read first, in the order an agent usually needs them. All absolute, all on the canonical host, all public.

| URL | Purpose |
| --- | --- |
| [https://www.kuzog.com/llms.txt](https://www.kuzog.com/llms.txt) | Short brief: what KUZOG is, the two ventures, key figures, when to use the site, how to call the API. Start here. |
| [https://www.kuzog.com/llms-full.txt](https://www.kuzog.com/llms-full.txt) | Every product, process, result and FAQ answer from the site in one Markdown file. |
| [https://www.kuzog.com/AGENTS.md](https://www.kuzog.com/AGENTS.md) | Instructions for AI agents: content access, the API, the MCP server, rate limits, and policies, in one file (also at `/agents.md` and `/.well-known/agents.md`). |
| [https://www.kuzog.com/openapi.json](https://www.kuzog.com/openapi.json) | OpenAPI 3.1 description of the HTTP API on this page. |
| [https://www.kuzog.com/sitemap.xml](https://www.kuzog.com/sitemap.xml) | Every indexable page URL with lastmod, on the canonical www host. |
| [https://www.kuzog.com/sitemap.md](https://www.kuzog.com/sitemap.md) | The same page list as Markdown links, grouped by section and language. |
| [https://www.kuzog.com/pages.json](https://www.kuzog.com/pages.json) | Every page in every language as JSON: path, language, title, description, url, Markdown twin, lastmod, type. The same data `GET /api/pages` serves, paginated. |
| [https://www.kuzog.com/robots.txt](https://www.kuzog.com/robots.txt) | Crawl policy. Everything is allowed; the major AI crawlers are named explicitly. |
| [https://www.kuzog.com/.well-known/ard.json](https://www.kuzog.com/.well-known/ard.json) | Agentic Resource Discovery manifest: the MCP server, the API, `/ask` and the agent files, as one machine-readable list. |
| [https://www.kuzog.com/.well-known/agent-skills/index.json](https://www.kuzog.com/.well-known/agent-skills/index.json) | Agent Skills discovery index, one entry per `SKILL.md` this site publishes. |
| [https://www.kuzog.com/docs/](https://www.kuzog.com/docs/) | This page. Also readable as Markdown with `Accept: text/markdown`. |

Brief first, then depth: `llms.txt` → `llms-full.txt` → the page itself as Markdown. To act on a user’s behalf, read the [Authentication](https://www.kuzog.com/docs/#authentication) section before calling the API.

## Structured data

Every prerendered page embeds schema.org JSON-LD in `<script type="application/ld+json">` blocks, generated from the same English content the page renders. Nothing in it is invented: every string is a stable identifier or lifted from the page copy.

| Type | Where | Notes |
| --- | --- | --- |
| `Organization` | Every page | KUZOG France, with Hydrobio and Microplantes as `subOrganization`, a `ContactPoint` (email) and a `PostalAddress`. |
| `WebSite` | `/` | The site node, linked to the organisation. |
| `BreadcrumbList` | Every page except `/` | Home → page; articles are Home → Blog → article. |
| `Product` | `/hydrobio/` | Hydrobio as a product with its headline figures as `additionalProperty`, linked to its `Brand` and maker. |
| `Organization` | `/microplantes/` | Microplantes as a laboratory, not a packaged good. |
| `FAQPage` | `/hydrobio/`, `/microplantes/` | Each page’s FAQ as `Question` / `Answer` pairs. |
| `Blog`, `ItemList` | `/blog/` | The journal and an ordered list of every article URL. |
| `BlogPosting` | `/blog/<slug>/` | One per article; author and publisher are the organisation. |
| `JobPosting` | `/careers/` | One per open role, from the same data the page lists. |

The `@id` of every node is an absolute URL on `www.kuzog.com`, so a node on one page can be referenced from another.

## Pricing

The API and the MCP server are free and need no key. KUZOG publishes no public price list for Hydrobio or Microplantes — ask for a quote at https://www.kuzog.com/contact/.

That covers everything documented on this page: every `/api/` endpoint, the MCP server, WebMCP, `/ask`, and every discovery file. None of it needs a plan, a quota to buy into, or a credit card.

## Changelog

2026-08-22 (later the same day) — `POST /api/contact` accepts `application/json` bodies; every `/api/` response carries `API-Version`; versioning and deprecation policy published; Markdown 404 bodies for agents on unknown page URLs.

| Date | Change |
| --- | --- |
| 2026-09-15 | `GET /api/pages` (cursor pagination, `Link: rel="next"`); `Idempotency-Key` on `POST /api/contact`, with `idempotency-key-reused` and `idempotency-key-in-flight`; `Link: rel="sunset"` on every `/api/` response; MCP server at `/mcp`; WebMCP tools in the browser; NLWeb `/ask`; `AGENTS.md`, `sitemap.md`, `pages.json`, `.md` twins with YAML frontmatter, the Agent Skills index and the ARD manifest. |
| 2026-08-22 | Initial publication: JSON error envelope and rate-limit headers on `/api/`, `GET /api/health`, OpenAPI 3.1 description, Markdown content negotiation, this page. |

## Sitemap

- [Sitemap (Markdown)](https://www.kuzog.com/sitemap.md): every page in every language, with its Markdown twin.
- [sitemap.xml](https://www.kuzog.com/sitemap.xml)
- [llms.txt](https://www.kuzog.com/llms.txt)

---

_Source: https://www.kuzog.com/docs/ · Markdown variant served via Accept: text/markdown · Full brief: https://www.kuzog.com/llms.txt_
