# REST API

> Updates to the REST API, newest first.

## 2026-10-25 · `1.0.0`

**REST API 1.0.0.** Version 1.0.0 of the Filmo REST API.

### Added

- `retryAfter` on the `429 rate_limited` problem (REST and MCP): seconds to wait before retrying, the same value as the `Retry-After` header; `detail` says it too.
- `GET /planner/services` rows carry `rowKind` (`summary` or `detail`): a service comes as one `summary` row (no assignee) plus one `detail` row per person or vehicle assigned, with the same `orderServiceId`. Group by `orderServiceId`; a null `staffMemberId` on the summary row does not mean uncovered.
- Every successful response (2xx) of every operation carries an `example`: deterministic, fictitious data (RFC 2606 domains) that validates against the response schema.
- One example response per error code (the 20 `code`s), as `components.examples.Problem_<code>` and referenced from the error responses of every operation, with the real `title` and shape of the problem.

### Changed

- Error `type` URIs are now `https://docs.filmo.io/errors/<code>` (they were `https://developers.filmo.io/errors/<code>`). Branch on `code`, not on the URI.
- Every 409 `conflict` now carries its cause in `errors[0].code`: `in_progress`, `lock_timeout` or `invalid_state`. Where `errors[].code` used to be `conflict` (unique violations, and the lines of an atomic batch that fail because their state forbids the operation) it is now `invalid_state`; a batch line that does not exist or has an invalid quantity still reports `not_found` or `validation_error`.
- Success bodies are named schemas with an optional `meta.warnings`; write responses declare the `Idempotent-Replay` header.
- The documentation moved to https://docs.filmo.io; in production `/api/v1/docs` redirects there.
- Date filters (`createdOn`, `issueDateBefore`, `returnTo`…) are published as a single `type: string` with a `pattern` (a bare day `YYYY-MM-DD` or an ISO 8601 instant with offset) instead of `anyOf` of `date` and `date-time`, which some client generators could not compile. The server accepts exactly the same values.
- The 401 and 403 responses no longer declare the `RateLimit-*` headers: they were never sent (those errors are answered before the request counts against the limit).
- Every operation that can answer 402 for a missing add-on names it in its `x-required-addon` extension (e.g. `["incidence"]`). `ProblemDetails.requiredAddon` and `AccountDto.addons` are now closed enums of add-on ids; `GET /account` only lists ids from that catalog.
- The `path` of the `errors[]` entries of a 409 `contact_in_use` (the type of record that uses the contact: `orders`, `projects`, `quotes`, `invoices`, `proformas`, `creditNotes`, `simpleCreditNotes`, `salesReceipts`, `supplierInvoices`, `contracts`, `subrents`, `purchaseOrders`, `sepaMandates`, `receipts`, `consignments`), `address_in_use` (always `orders`) and `insufficient_stock` (`items` or `spaces`) is a closed set, published per code in the `x-path-values` extension of `ProblemDetails.errors[].path`.
- `Idempotency-Key` is declared with `minLength: 1`. An empty key was already a 400 `validation_error` over REST; the MCP tools now reject an empty `idempotencyKey` the same way (`errors[0].path` = `idempotencyKey`) instead of ignoring it and running the write unprotected.

### Fixed

- Every MCP tool result now validates with the official SDKs (`Client.callTool` in TypeScript and Python): successes with `meta.idempotencyKey` and every error used to fail with `-32602 Structured content does not match the tool's output schema`.
- Detail reads sent with `If-None-Match` now answer `304 Not Modified` (no body, with `ETag`) when the tag is current, as the spec already declared. Until now they returned a full 200.
- Without `Accept-Language`, the 401s of a key that exists (revoked, expired, owner inactive or not an administrator) and the 403 `insufficient_scope` came back in Spanish whatever the owner of the key preferred. They now use the preferred language of the owner, like every later error; a request with no credential or an unknown key is still Spanish.
- `upsertExternalInvoice` documented customer and totals problems as 422; they are 400 `validation_error`, and `errors[0].path` now names the field (`contactId`, `contactTaxId`, `contactName`, `subTotal`, `taxTotal`, `total`, `lines[n].taxRate`).

---

Canonical URL: https://docs.filmo.io/changelog/rest-api  
Last updated: 2026-10-25