# Changelog

Changes to the Ollav REST API, MCP server and docs, newest first. Feeds: [Atom](https://ollav.app/docs/changelog.atom), [RSS](https://ollav.app/docs/changelog.rss).

## API versions {#versions}

Breaking changes ship as dated versions; additive ones (new endpoints, new fields) reach every version. A request runs at the version in its `Ollav-Version` header, or else at the version its API key was pinned to when it was created (see `apiVersion` in `GET /api/keys`). Every `/api` response names the version it used in its `Ollav-Version` header. To upgrade, test with the header, then send it everywhere or create a new key.

| Version | Change | Notes |
|---|---|---|
| `2026-11-01` | POST /api/comments returns breadcrumbs as an array, like GET /api/comments | Latest: new API keys are pinned to it |
| `2026-10-01` | Baseline | The API before versioning; keys created before 2026-10-27 |

## 2026-11-01: POST /api/comments returns breadcrumbs as an array {#2026-11-01-post-apicomments-returns-breadcrumbs-as-an-array}

**Breaking** · API version `2026-11-01`

The comment `POST /api/comments` creates is returned with `breadcrumbs` as an array of `{ selector, text }`, or null, the same as `GET /api/comments` and `GET /api/review`. Before, it was the stored JSON string.

Who is affected: only requests at version 2026-11-01. API keys created from now on are pinned to it; existing keys stay on 2026-10-01 and keep receiving the string until they send `Ollav-Version: 2026-11-01` or are replaced.

## 2026-10-28: Abuse limits: publishes, comments and upload files {#2026-10-28-abuse-limits-publishes-comments-and-upload-files}

**Added**

Publishing (`POST /api/documents` and `POST /api/documents/{slug}/versions` together) is limited to 30 an hour per account and comments (`POST /api/comments`, replies included) to 120 an hour per account, with looser limits per IP address. Over a limit the API answers `429 RATE_LIMITED` with a `Retry-After` header and `error.retry_after` in seconds; MCP tools return the same error. A multipart upload takes one file (`400 TOO_MANY_FILES`); the 15 MB document limit is unchanged. New error codes: `ACCOUNT_SUSPENDED` (403, every request from an account suspended for breaking the Terms) and `DOCUMENT_REMOVED` (410, publishing to a document taken down for breaking the Terms). See [/docs/errors](https://ollav.app/docs/errors).

## 2026-10-28: Email sign-in codes: LOGIN_CODE_INVALID and SIGNIN_RATE_LIMITED {#2026-10-28-email-sign-in-codes-logincodeinvalid-and-signinratelimited}

**Added**

Signing in by email now sends a magic link and a 6-digit code in one email; either works. Two error codes are new: `LOGIN_CODE_INVALID` (a sign-in code that is wrong, expired, used, replaced by a newer email or spent by 5 wrong tries) and `SIGNIN_RATE_LIMITED` (too many sign-in emails or code tries in an hour). Both are on [/docs/errors](https://ollav.app/docs/errors). They come from the web sign-in form only; no API-key endpoint changes.

## 2026-10-27: MCP docs, a REST API reference, two quickstarts, and typed errors on every MCP error {#2026-10-27-mcp-docs-a-rest-api-reference-two-quickstarts-and-typed-errors-on-every-mcp-error}

**Added**

New: [/docs/mcp](https://ollav.app/docs/mcp) (connecting each client, auth options, checking the connection, every tool with its inputs, outputs, annotations and an example, and troubleshooting), [/docs/api](https://ollav.app/docs/api) (every REST endpoint with a curl example, generated from the OpenAPI spec) and two quickstarts: [curl](https://ollav.app/docs/quickstart/curl) and [MCP](https://ollav.app/docs/quickstart/mcp).

MCP: every error now carries the same `code`, `hint` and `doc_url` as REST. Protocol errors (no or invalid key, unknown tool or method, bad JSON) carry them in the JSON-RPC `error.data`; their `code` and `message` are unchanged. A tool call missing a required argument (e.g. `slug`) now returns `VALIDATION_ERROR` naming it instead of a 404, and an unexpected failure returns `INTERNAL_ERROR` as a tool error. Each tool description now opens with when to use it.

## 2026-10-27: Test mode counts towards no plan limit; test keys get 60 requests a minute {#2026-10-27-test-mode-counts-towards-no-plan-limit-test-keys-get-60-requests-a-minute}

**Changed**

Requests with an `ol_test_` key no longer count towards any plan limit: no daily API-call quota, and no document, storage or version-history limit in the sandbox (its `GET /api/workspaces/{id}/billing` reports `enabled: false`). Instead each test key is limited to 60 requests a minute: the 61st answers `429 RATE_LIMITED` with `Retry-After: 60`. Every API-key operation now documents its 429. Test documents still expire 30 days after upload. Live keys are unchanged.

## 2026-10-27: Test mode: ol_test_ keys and a sandbox per workspace {#2026-10-27-test-mode-oltest-keys-and-a-sandbox-per-workspace}

**Added**

API keys now come in two modes. `ol_live_…` keys work on live data, as before. `ol_test_…` keys (create them at [/keys](https://app.ollav.app/keys), or `POST /api/keys` with `mode: "test"` from the web UI) work in your own test sandbox of the workspace: they can't read live documents and live keys can't read theirs, nothing counts towards the plan, and test documents are marked TEST on their page and deleted 30 days after upload. `GET /api/me`, uploads and new versions return `livemode`; `GET /api/keys` returns each key's `mode`. Keys can be rotated (replaced in one step) from the keys page. New live keys start `ol_live_`; existing `sk_live_` keys keep working unchanged.

## 2026-10-27: API versions and the Ollav-Version header {#2026-10-27-api-versions-and-the-ollav-version-header}

**Added**

Breaking changes now ship as dated API versions. Each request runs at the version in its `Ollav-Version` header, or else at the version its API key was pinned to when it was created (`apiVersion` in `GET /api/keys`). Every `/api` response says which version it used in its `Ollav-Version` header. An unknown version is a 400 `INVALID_API_VERSION`. The first version, 2026-10-01, is the API as it was before versioning.

## 2026-10-27: Idempotency-Key on every state-changing request {#2026-10-27-idempotency-key-on-every-state-changing-request}

**Added**

Send `Idempotency-Key: <unique string>` on any POST, PATCH or DELETE. A retry with the same key and the same request returns the first successful response, with `Idempotent-Replayed: true`, instead of doing the action twice; after an error the key is free for a corrected retry. Reusing a key for a different request is a 409 `IDEMPOTENCY_KEY_REUSED`; retrying while the first is still running is a 409 `IDEMPOTENCY_KEY_IN_USE`. Keys are kept for 24 hours, per API key. MCP tools that change something take an optional `idempotency_key` argument.

## 2026-10-27: Typed errors and Request-Id {#2026-10-27-typed-errors-and-request-id}

**Added**

Every `/api` error now carries `type` (invalid_request_error, authentication_error, permission_error, not_found_error, rate_limit_error, idempotency_error or api_error), `param` when one field is at fault, `doc_url` (an anchor on [/docs/errors](https://ollav.app/docs/errors)) and `request_id`, alongside the existing `code`, `message` and `hint`. Error codes are unchanged. Every response carries a `Request-Id` header; quote it when reporting a problem. MCP tool errors carry the same `code` and `doc_url`.

## 2026-10-27: OpenAPI spec covers every public endpoint {#2026-10-27-openapi-spec-covers-every-public-endpoint}

**Added**

[/openapi.json](https://ollav.app/openapi.json) is generated from one source file and checked against the running API in CI. Newly documented: `GET /api/me`, `GET /api/workspaces`, `GET /api/workspaces/{id}/members`, `GET /api/workspaces/{id}/billing`, `GET /api/keys`, `DELETE /api/keys/{id}` and `GET /api/exports/download`.
