{"openapi":"3.1.0","info":{"title":"Levano API","version":"1.0.0","description":"Levano's organisation-scoped open API. Authenticate with\n`Authorization: Bearer lev_live_\u2026` (production) or `lev_test_\u2026` (non-production).\nA key belongs to exactly one organisation and can never read another's data.\nMint, rotate and revoke keys under Settings \u2192 API-n\u00f8gler.\n\n**Guides and examples: [https://docs.levano.io](https://docs.levano.io).** This document is the contract;\nthe portal is how to use it.\n\n**Scopes.** Named `resource:action`. Required scope is listed on each\noperation. v1 wires `matters:read`, `clients:read`, `time:read`,\n`invoices:read`, `reports:read`, `documents:read`, `webhooks:manage`\nand the four import scopes below. `settings:read` and `settings:write`\nare reserved names and have no endpoints yet. Catalogue: `matters:read`, `matters:write`, `clients:read`, `clients:write`, `parties:write`, `time:read`, `time:write`, `invoices:read`, `reports:read`, `documents:read`, `settings:read`, `settings:write`, `webhooks:manage`.\n\n**Importing (create only).** `parties:write` creates companies and\npeople, `clients:write` opens engagements on them, `matters:write`\nopens sager on those, and `time:write` lands the hours. There is no\n`PATCH` and no `DELETE` anywhere in v1: Levano stays the system of\nrecord, and an external key must not be able to rewrite or erase a\nclient relationship, a sag or a billed hour. Import in this order,\nbecause each step points at the row before it and none of them can\nconjure it:\n\n1. `POST /companies` or `POST /people` for the party. Keep the returned `id`.\n2. `POST /clients` with **exactly one** of `company_id` / `person_id`.\n3. `POST /matters` with the `client_id` from step 2.\n4. `POST /time-entries` with the `matter_id` from step 3.\n\nSending both party ids, or neither, is a `422`. An id from another\norganisation does not exist here and is a `422` as well. One party\nholds at most one engagement, so a repeat is a `422` naming the\nengagement that has it. `kyc_status` opens at `pending` for every\nimported engagement, and a time entry opens at `draft`.\n\n**Hvidvaskloven reaches the import.** A matter cannot open `active`\nwhile the client's KYC case is missing, unapproved or expired: that is\nsection 14, and it holds for a key exactly as it holds for a lawyer.\nImport historical matters as `dormant` and activate them in Levano once\nthe kundekendskabsprocedure is approved. The section 14(2) exemption is\nnot available over the API, because it is an audited decision by a\nnamed person holding a specific permission.\n\n**Derived values are refused, not ignored.** A time entry's `amount` is\ncomputed from `duration_minutes` and `hourly_rate` on every save, so\nsending one is a `422` rather than a silent recomputation into a\ndifferent number. Same for a time entry's `status`.\n\n**Idempotency.** Every `POST` requires an `Idempotency-Key` header\n(max 255 characters, typically the source-system id of the row). The\nsame key and the same body replays the original resource as `200` with\n`Idempotent-Replayed: true` and creates nothing. The same key with a\ndifferent body is `409`, and so is a duplicate that arrives while the\nfirst call is still running. Keys are honoured for 24 hours and are\nscoped per endpoint, so one source id can key both the company POST and\nthe client POST that follows it. A request that failed does not burn its\nkey: correct the payload and send it again under the same one.\n\n**CPR is never accepted, in either direction.** It is not returned on\nany resource, and `POST /people` rejects a `cpr` field with a `422`\nrather than dropping it silently. Levano collects it from the client\nthrough the KYC flow, which is the only path that may hold it.\n\n**Documents are metadata only.** `documents:read` returns the register:\nname, filename, MIME type, size, matter and folder. It never returns the\nfile itself, a download URL or a preview. Sending privileged legal\nmaterial out over an API is a product decision we have not taken; until\nwe do, the bytes stay on the platform.\n\n**Versioning.** URL-versioned: `/api/v1`.\nAdditive changes do not bump. New fields, new endpoints and new optional\nquery parameters are v1 changes. Clients MUST ignore unknown fields.\nRemovals, renames and semantic changes bump: `/api/v2` will run side by\nside. Deprecation is announced with `Deprecation` and `Sunset` headers\nfor a minimum of six months before a version is removed.\n\n**Pagination.** List endpoints use cursor pagination, not offset.\nPass `per_page` (default 50, max 100). Follow `links.next` (opaque\n`cursor` query parameter) until it is `null`.\n\n**Money.** Amounts are integers in \u00f8re (minor units) plus a `currency`\ncode (`DKK`). Never a decimal, in either direction: a `POST` carrying\n`1250.00` where the contract says \u00f8re is a `422`, not a rounded\n`1250`. A body that means 12,50 kr sends `1250`.\n\n**Parties.** A client is an engagement, not a party register. Its\n`company_id` and `person_id` resolve through `/companies/{id}` and\n`/people/{id}`, which is how a migration reconciles stamdata when one\nparty holds several engagements. Both read with `clients:read`.\nPersonal identifiers (CPR) are never exposed, in any form.\n\n**Rate limits.** Per API client, not per IP (default 120/min).\nEvery authenticated response carries `X-RateLimit-Limit`,\n`X-RateLimit-Remaining` and `X-RateLimit-Reset`. `429` also carries\n`Retry-After`.\n\n**Errors.** JSON `{ \"message\": \"\u2026\" }`. `403` adds `required_scope`.\nA missing resource in this organisation is `404`, never `403`. A\nforeign id must not confirm that the row exists elsewhere.\n\n**Webhooks.** Subscribe with `webhooks:manage`. Deliveries are signed\n`Levano-Signature: t=<unix>,v1=<hex>` (HMAC-SHA256 over `{t}.{raw body}`).\nVerify the received bytes, never re-serialised JSON. At-least-once:\ndeduplicate on `id`. Five attempts with exponential backoff; after 20\nconsecutive failures the endpoint is disabled."},"servers":[{"url":"https://app.levano.io/api/v1"}],"externalDocs":{"description":"Guides, examples and the rendered reference.","url":"https://docs.levano.io/guides/getting-started/"},"security":[{"bearerAuth":[]}],"tags":[{"name":"Matters","description":"Cases (`matters:read`, create with `matters:write`)."},{"name":"Clients","description":"Engagements (`clients:read`, create with `clients:write`)."},{"name":"People","description":"Person parties behind engagements (`clients:read`, create with `parties:write`)."},{"name":"Companies","description":"Company parties behind engagements (`clients:read`, create with `parties:write`)."},{"name":"Time entries","description":"Time register (`time:read`, create with `time:write`)."},{"name":"Invoices","description":"Invoices; amounts in \u00f8re (`invoices:read`)."},{"name":"Documents","description":"Document metadata; never bytes (`documents:read`)."},{"name":"Reports","description":"Computed reports (`reports:read`)."},{"name":"Webhooks","description":"Outbound endpoint CRUD (`webhooks:manage`)."}],"paths":{"/matters":{"get":{"tags":["Matters"],"summary":"List matters","description":"Requires `matters:read`. Cursor-paginated. Filters combine with AND and are all optional; an unknown query parameter is ignored. An id from another organisation returns an empty page, not an error. `links.next` carries the filters forward.","parameters":[{"name":"client_id","in":"query","required":false,"description":"Only matters belonging to this client.","schema":{"type":"string","format":"uuid"}},{"name":"status","in":"query","required":false,"description":"Only matters in this status.","schema":{"type":"string","enum":["active","dormant","closed","archived"]}},{"name":"updated_since","in":"query","required":false,"description":"Only matters changed at or after this instant. ISO-8601; URL-encode the `+` of an offset, or send `Z`.","schema":{"type":"string","format":"date-time"}},{"name":"per_page","in":"query","required":false,"description":"Page size (default 50, max 100).","schema":{"type":"integer"}},{"name":"cursor","in":"query","required":false,"description":"Opaque cursor from the previous `links.next`.","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["data","meta","links"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Matter"}},"meta":{"$ref":"#/components/schemas/CursorMeta"},"links":{"$ref":"#/components/schemas/CursorLinks"}}}}}},"401":{"description":"Missing or invalid bearer token","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing `matters:read`","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Forbidden"}}}},"422":{"description":"Invalid filters","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"429":{"description":"Too many requests. Honour `Retry-After`.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Matters"],"summary":"Create a matter","description":"Requires `matters:write` and an `Idempotency-Key` header. Create-only: v1 has no PATCH and no DELETE on this resource. The engagement must exist first: POST `/clients`, then POST `/matters` with the returned `client_id`. Levano assigns the next `SAG-` number unless you send `matter_number`, and a migration usually should. Hvidvasklovens section 14 applies here exactly as it does in the app, so a matter cannot open `active` while the client's KYC case is missing, unapproved or expired. That is a `422` on `status`, and the message says what to do: import the sag as `dormant` and activate it in Levano once the kundekendskabsprocedure is approved. The section 14(2) exemption is deliberately unreachable over the API, because it is an audited decision by a named person holding a specific permission, and a key is nobody.","parameters":[{"name":"Idempotency-Key","in":"header","required":true,"description":"Required, not optional: an import that cannot be replayed safely is one nobody dares retry. Any string up to 255 characters that is stable for the row you are importing, e.g. the source system id. Honoured for 24 hours, and scoped per endpoint, so the same source id may key the company POST and the client POST that follows it. A request that failed does not burn the key: fix the payload and send it again under the same one.","schema":{"type":"string","maxLength":255}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MatterWrite"}}}},"responses":{"201":{"description":"Created. The body is the same shape the matching `GET` returns.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"},"Location":{"description":"URL of the created resource.","schema":{"type":"string","format":"uri"}}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Matter"}}}}}},"200":{"description":"Replay. This `Idempotency-Key` and body already created a resource, and this is that same resource. Nothing was created a second time.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"},"Idempotent-Replayed":{"description":"Present and `true` on a replay. Absent on the original `201`.","schema":{"type":"string","enum":["true"]}}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Matter"}}}}}},"401":{"description":"Missing or invalid bearer token","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing `matters:write`","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Forbidden"}}}},"409":{"description":"The same `Idempotency-Key` was already used with a different body, or a request carrying it is still running. Neither is retryable as sent: use a new key for a new payload, or wait for the first call to answer.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Invalid payload, or a missing `Idempotency-Key`","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"429":{"description":"Too many requests. Honour `Retry-After`.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/matters/{matter}":{"get":{"tags":["Matters"],"summary":"Read a matter","description":"Requires `matters:read`.","parameters":[{"name":"matter","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Matter"}}}}}},"401":{"description":"Missing or invalid bearer token","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing `matters:read`","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Forbidden"}}}},"404":{"description":"No such resource in this organisation","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. Honour `Retry-After`.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/clients":{"get":{"tags":["Clients"],"summary":"List clients","description":"Requires `clients:read`. Cursor-paginated. Filters combine with AND and are all optional; an unknown query parameter is ignored. An id from another organisation returns an empty page, not an error. `links.next` carries the filters forward.","parameters":[{"name":"type","in":"query","required":false,"description":"Only clients of this party kind.","schema":{"type":"string","enum":["company","person"]}},{"name":"updated_since","in":"query","required":false,"description":"Only clients changed at or after this instant. ISO-8601; URL-encode the `+` of an offset, or send `Z`.","schema":{"type":"string","format":"date-time"}},{"name":"per_page","in":"query","required":false,"description":"Page size (default 50, max 100).","schema":{"type":"integer"}},{"name":"cursor","in":"query","required":false,"description":"Opaque cursor from the previous `links.next`.","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["data","meta","links"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Client"}},"meta":{"$ref":"#/components/schemas/CursorMeta"},"links":{"$ref":"#/components/schemas/CursorLinks"}}}}}},"401":{"description":"Missing or invalid bearer token","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing `clients:read`","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Forbidden"}}}},"422":{"description":"Invalid filters","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"429":{"description":"Too many requests. Honour `Retry-After`.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Clients"],"summary":"Create a client engagement","description":"Requires `clients:write` and an `Idempotency-Key` header. Create-only: v1 has no PATCH and no DELETE on this resource. The engagement carries no stamdata of its own, so the party must exist first. Send exactly one of `company_id` or `person_id`; neither, or both, is a `422`. An id belonging to another organisation does not exist here and is a `422` as well, never a cross-tenant engagement. One party holds at most one engagement: a second attempt is a `422` naming the engagement that already has it.","parameters":[{"name":"Idempotency-Key","in":"header","required":true,"description":"Required, not optional: an import that cannot be replayed safely is one nobody dares retry. Any string up to 255 characters that is stable for the row you are importing, e.g. the source system id. Honoured for 24 hours, and scoped per endpoint, so the same source id may key the company POST and the client POST that follows it. A request that failed does not burn the key: fix the payload and send it again under the same one.","schema":{"type":"string","maxLength":255}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClientWrite"}}}},"responses":{"201":{"description":"Created. The body is the same shape the matching `GET` returns.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"},"Location":{"description":"URL of the created resource.","schema":{"type":"string","format":"uri"}}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Client"}}}}}},"200":{"description":"Replay. This `Idempotency-Key` and body already created a resource, and this is that same resource. Nothing was created a second time.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"},"Idempotent-Replayed":{"description":"Present and `true` on a replay. Absent on the original `201`.","schema":{"type":"string","enum":["true"]}}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Client"}}}}}},"401":{"description":"Missing or invalid bearer token","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing `clients:write`","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Forbidden"}}}},"409":{"description":"The same `Idempotency-Key` was already used with a different body, or a request carrying it is still running. Neither is retryable as sent: use a new key for a new payload, or wait for the first call to answer.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Invalid payload, or a missing `Idempotency-Key`","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"429":{"description":"Too many requests. Honour `Retry-After`.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/clients/{client}":{"get":{"tags":["Clients"],"summary":"Read a client","description":"Requires `clients:read`.","parameters":[{"name":"client","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Client"}}}}}},"401":{"description":"Missing or invalid bearer token","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing `clients:read`","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Forbidden"}}}},"404":{"description":"No such resource in this organisation","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. Honour `Retry-After`.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/people":{"get":{"tags":["People"],"summary":"List people","description":"Requires `clients:read`. Cursor-paginated. Filters combine with AND and are all optional; an unknown query parameter is ignored. An id from another organisation returns an empty page, not an error. `links.next` carries the filters forward. The party register behind engagements. Resolve a client's `person_id` here. CPR is never returned.","parameters":[{"name":"updated_since","in":"query","required":false,"description":"Only rows changed at or after this instant. ISO-8601 date or date-time, e.g. `2026-09-01` or `2026-09-01T08:00:00Z`. URL-encode a `+` offset (`%2B02:00`), or it arrives as a space and the request is rejected 422.","schema":{"type":"string"}},{"name":"per_page","in":"query","required":false,"description":"Page size (default 50, max 100).","schema":{"type":"integer"}},{"name":"cursor","in":"query","required":false,"description":"Opaque cursor from the previous `links.next`.","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["data","meta","links"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Person"}},"meta":{"$ref":"#/components/schemas/CursorMeta"},"links":{"$ref":"#/components/schemas/CursorLinks"}}}}}},"401":{"description":"Missing or invalid bearer token","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing `clients:read`","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Forbidden"}}}},"422":{"description":"Invalid filters","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"429":{"description":"Too many requests. Honour `Retry-After`.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["People"],"summary":"Create a person","description":"Requires `parties:write` and an `Idempotency-Key` header. Create-only: v1 has no PATCH and no DELETE on this resource. Stamdata only. This does not open an engagement: POST the person, then POST `/clients` with the returned `person_id`. `cpr` is rejected with a `422`. It is never accepted over the API, and the KYC flow collects it from the client directly. An e-mail already held by another person in the organisation is a `422`.","parameters":[{"name":"Idempotency-Key","in":"header","required":true,"description":"Required, not optional: an import that cannot be replayed safely is one nobody dares retry. Any string up to 255 characters that is stable for the row you are importing, e.g. the source system id. Honoured for 24 hours, and scoped per endpoint, so the same source id may key the company POST and the client POST that follows it. A request that failed does not burn the key: fix the payload and send it again under the same one.","schema":{"type":"string","maxLength":255}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PersonWrite"}}}},"responses":{"201":{"description":"Created. The body is the same shape the matching `GET` returns.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"},"Location":{"description":"URL of the created resource.","schema":{"type":"string","format":"uri"}}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Person"}}}}}},"200":{"description":"Replay. This `Idempotency-Key` and body already created a resource, and this is that same resource. Nothing was created a second time.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"},"Idempotent-Replayed":{"description":"Present and `true` on a replay. Absent on the original `201`.","schema":{"type":"string","enum":["true"]}}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Person"}}}}}},"401":{"description":"Missing or invalid bearer token","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing `parties:write`","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Forbidden"}}}},"409":{"description":"The same `Idempotency-Key` was already used with a different body, or a request carrying it is still running. Neither is retryable as sent: use a new key for a new payload, or wait for the first call to answer.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Invalid payload, or a missing `Idempotency-Key`","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"429":{"description":"Too many requests. Honour `Retry-After`.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/people/{person}":{"get":{"tags":["People"],"summary":"Read a person","description":"Requires `clients:read`.","parameters":[{"name":"person","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Person"}}}}}},"401":{"description":"Missing or invalid bearer token","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing `clients:read`","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Forbidden"}}}},"404":{"description":"No such resource in this organisation","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. Honour `Retry-After`.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/companies":{"get":{"tags":["Companies"],"summary":"List companies","description":"Requires `clients:read`. Cursor-paginated. Filters combine with AND and are all optional; an unknown query parameter is ignored. An id from another organisation returns an empty page, not an error. `links.next` carries the filters forward. The party register behind engagements. Resolve a client's `company_id` here.","parameters":[{"name":"updated_since","in":"query","required":false,"description":"Only rows changed at or after this instant. ISO-8601 date or date-time, e.g. `2026-09-01` or `2026-09-01T08:00:00Z`. URL-encode a `+` offset (`%2B02:00`), or it arrives as a space and the request is rejected 422.","schema":{"type":"string"}},{"name":"cvr","in":"query","required":false,"description":"Exact CVR match. Non-digits are ignored, so `12 34 56 78` and `12345678` are the same query.","schema":{"type":"string"}},{"name":"per_page","in":"query","required":false,"description":"Page size (default 50, max 100).","schema":{"type":"integer"}},{"name":"cursor","in":"query","required":false,"description":"Opaque cursor from the previous `links.next`.","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["data","meta","links"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Company"}},"meta":{"$ref":"#/components/schemas/CursorMeta"},"links":{"$ref":"#/components/schemas/CursorLinks"}}}}}},"401":{"description":"Missing or invalid bearer token","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing `clients:read`","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Forbidden"}}}},"422":{"description":"Invalid filters","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"429":{"description":"Too many requests. Honour `Retry-After`.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Companies"],"summary":"Create a company","description":"Requires `parties:write` and an `Idempotency-Key` header. Create-only: v1 has no PATCH and no DELETE on this resource. Stamdata only. This does not open an engagement: POST the company, then POST `/clients` with the returned `company_id`. A CVR already held by another company in the organisation is a `422`, and the error names the id that holds it. The CVR register snapshot and the logo are fetched in the background, exactly as they are when the company is created in the app.","parameters":[{"name":"Idempotency-Key","in":"header","required":true,"description":"Required, not optional: an import that cannot be replayed safely is one nobody dares retry. Any string up to 255 characters that is stable for the row you are importing, e.g. the source system id. Honoured for 24 hours, and scoped per endpoint, so the same source id may key the company POST and the client POST that follows it. A request that failed does not burn the key: fix the payload and send it again under the same one.","schema":{"type":"string","maxLength":255}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompanyWrite"}}}},"responses":{"201":{"description":"Created. The body is the same shape the matching `GET` returns.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"},"Location":{"description":"URL of the created resource.","schema":{"type":"string","format":"uri"}}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Company"}}}}}},"200":{"description":"Replay. This `Idempotency-Key` and body already created a resource, and this is that same resource. Nothing was created a second time.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"},"Idempotent-Replayed":{"description":"Present and `true` on a replay. Absent on the original `201`.","schema":{"type":"string","enum":["true"]}}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Company"}}}}}},"401":{"description":"Missing or invalid bearer token","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing `parties:write`","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Forbidden"}}}},"409":{"description":"The same `Idempotency-Key` was already used with a different body, or a request carrying it is still running. Neither is retryable as sent: use a new key for a new payload, or wait for the first call to answer.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Invalid payload, or a missing `Idempotency-Key`","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"429":{"description":"Too many requests. Honour `Retry-After`.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/companies/{company}":{"get":{"tags":["Companies"],"summary":"Read a company","description":"Requires `clients:read`.","parameters":[{"name":"company","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Company"}}}}}},"401":{"description":"Missing or invalid bearer token","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing `clients:read`","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Forbidden"}}}},"404":{"description":"No such resource in this organisation","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. Honour `Retry-After`.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/time-entries":{"get":{"tags":["Time entries"],"summary":"List time entries","description":"Requires `time:read`. Cursor-paginated. Filters combine with AND and are all optional; an unknown query parameter is ignored. An id from another organisation returns an empty page, not an error. `links.next` carries the filters forward.","parameters":[{"name":"matter_id","in":"query","required":false,"description":"Only entries on this matter.","schema":{"type":"string","format":"uuid"}},{"name":"client_id","in":"query","required":false,"description":"Only entries on matters belonging to this client. Entries with no matter are excluded.","schema":{"type":"string","format":"uuid"}},{"name":"user_id","in":"query","required":false,"description":"Only entries logged by this user.","schema":{"type":"string","format":"uuid"}},{"name":"date_from","in":"query","required":false,"description":"Inclusive earliest entry date (YYYY-MM-DD).","schema":{"type":"string","format":"date"}},{"name":"date_to","in":"query","required":false,"description":"Inclusive latest entry date (YYYY-MM-DD).","schema":{"type":"string","format":"date"}},{"name":"status","in":"query","required":false,"description":"Only entries in this status.","schema":{"type":"string","enum":["draft","submitted","approved","invoiced","written_off"]}},{"name":"per_page","in":"query","required":false,"description":"Page size (default 50, max 100).","schema":{"type":"integer"}},{"name":"cursor","in":"query","required":false,"description":"Opaque cursor from the previous `links.next`.","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["data","meta","links"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/TimeEntry"}},"meta":{"$ref":"#/components/schemas/CursorMeta"},"links":{"$ref":"#/components/schemas/CursorLinks"}}}}}},"401":{"description":"Missing or invalid bearer token","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing `time:read`","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Forbidden"}}}},"422":{"description":"Invalid filters","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"429":{"description":"Too many requests. Honour `Retry-After`.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Time entries"],"summary":"Create a time entry","description":"Requires `time:write` and an `Idempotency-Key` header. Create-only: v1 has no PATCH and no DELETE on this resource. The last step of an import: the matter must exist first. `amount` is computed from `duration_minutes` and `hourly_rate`, and sending it is a `422` rather than a silent recomputation. A rate left out falls back to the matter's own `hourly_rate`. Every imported entry opens at `draft`. This is the one create with no `Location` header: v1 has no single-entry read to point at. Find the entry again on `GET /time-entries` with `matter_id` and a date range.","parameters":[{"name":"Idempotency-Key","in":"header","required":true,"description":"Required, not optional: an import that cannot be replayed safely is one nobody dares retry. Any string up to 255 characters that is stable for the row you are importing, e.g. the source system id. Honoured for 24 hours, and scoped per endpoint, so the same source id may key the company POST and the client POST that follows it. A request that failed does not burn the key: fix the payload and send it again under the same one.","schema":{"type":"string","maxLength":255}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TimeEntryWrite"}}}},"responses":{"201":{"description":"Created. The body is the same shape the matching `GET` returns.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/TimeEntry"}}}}}},"200":{"description":"Replay. This `Idempotency-Key` and body already created a resource, and this is that same resource. Nothing was created a second time.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"},"Idempotent-Replayed":{"description":"Present and `true` on a replay. Absent on the original `201`.","schema":{"type":"string","enum":["true"]}}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/TimeEntry"}}}}}},"401":{"description":"Missing or invalid bearer token","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing `time:write`","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Forbidden"}}}},"409":{"description":"The same `Idempotency-Key` was already used with a different body, or a request carrying it is still running. Neither is retryable as sent: use a new key for a new payload, or wait for the first call to answer.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Invalid payload, or a missing `Idempotency-Key`","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"429":{"description":"Too many requests. Honour `Retry-After`.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/invoices":{"get":{"tags":["Invoices"],"summary":"List invoices","description":"Requires `invoices:read`. Cursor-paginated. Filters combine with AND and are all optional; an unknown query parameter is ignored. An id from another organisation returns an empty page, not an error. `links.next` carries the filters forward. Lines are omitted on the index.","parameters":[{"name":"client_id","in":"query","required":false,"description":"Only invoices for this client.","schema":{"type":"string","format":"uuid"}},{"name":"matter_id","in":"query","required":false,"description":"Only invoices for this matter.","schema":{"type":"string","format":"uuid"}},{"name":"status","in":"query","required":false,"description":"Only invoices in this status.","schema":{"type":"string","enum":["draft","approved","sent","partially_paid","overdue","paid","cancelled","credited"]}},{"name":"issue_date_from","in":"query","required":false,"description":"Inclusive earliest issue date (YYYY-MM-DD).","schema":{"type":"string","format":"date"}},{"name":"issue_date_to","in":"query","required":false,"description":"Inclusive latest issue date (YYYY-MM-DD).","schema":{"type":"string","format":"date"}},{"name":"per_page","in":"query","required":false,"description":"Page size (default 50, max 100).","schema":{"type":"integer"}},{"name":"cursor","in":"query","required":false,"description":"Opaque cursor from the previous `links.next`.","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["data","meta","links"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Invoice"}},"meta":{"$ref":"#/components/schemas/CursorMeta"},"links":{"$ref":"#/components/schemas/CursorLinks"}}}}}},"401":{"description":"Missing or invalid bearer token","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing `invoices:read`","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Forbidden"}}}},"422":{"description":"Invalid filters","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"429":{"description":"Too many requests. Honour `Retry-After`.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/invoices/{invoice}":{"get":{"tags":["Invoices"],"summary":"Read an invoice","description":"Requires `invoices:read`. Includes `lines`.","parameters":[{"name":"invoice","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Invoice"}}}}}},"401":{"description":"Missing or invalid bearer token","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing `invoices:read`","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Forbidden"}}}},"404":{"description":"No such resource in this organisation","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. Honour `Retry-After`.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/documents":{"get":{"tags":["Documents"],"summary":"List documents","description":"Requires `documents:read`. Cursor-paginated. Filters combine with AND and are all optional; an unknown query parameter is ignored. An id from another organisation returns an empty page, not an error. `links.next` carries the filters forward. Metadata only: no bytes and no download URL (see the Documents tag).","parameters":[{"name":"matter_id","in":"query","required":false,"description":"Only documents on this matter.","schema":{"type":"string","format":"uuid"}},{"name":"per_page","in":"query","required":false,"description":"Page size (default 50, max 100).","schema":{"type":"integer"}},{"name":"cursor","in":"query","required":false,"description":"Opaque cursor from the previous `links.next`.","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["data","meta","links"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Document"}},"meta":{"$ref":"#/components/schemas/CursorMeta"},"links":{"$ref":"#/components/schemas/CursorLinks"}}}}}},"401":{"description":"Missing or invalid bearer token","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing `documents:read`","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Forbidden"}}}},"422":{"description":"Invalid filters","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"429":{"description":"Too many requests. Honour `Retry-After`.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/documents/{document}":{"get":{"tags":["Documents"],"summary":"Read a document","description":"Requires `documents:read`. Metadata only.","parameters":[{"name":"document","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Document"}}}}}},"401":{"description":"Missing or invalid bearer token","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing `documents:read`","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Forbidden"}}}},"404":{"description":"No such resource in this organisation","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. Honour `Retry-After`.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/reports/{type}":{"get":{"tags":["Reports"],"summary":"Compute a report","description":"Requires `reports:read`. `{type}` is a kebab slug from the reports hub. The same compute path the in-app reports use, serialised as JSON. Default date range is the current month; the window may not exceed two years.","parameters":[{"name":"type","in":"path","required":true,"schema":{"type":"string"},"description":"Kebab slug, e.g. `revenue`, `ar-aging`, `time-distribution`."},{"name":"date_from","in":"query","required":false,"description":"Inclusive start date (YYYY-MM-DD).","schema":{"type":"string"}},{"name":"date_to","in":"query","required":false,"description":"Inclusive end date (YYYY-MM-DD).","schema":{"type":"string"}},{"name":"preset","in":"query","required":false,"description":"Date preset (`this_month`, `last_month`, `this_quarter`, `last_quarter`, `ytd`, `last_7d`, `last_30d`, `last_90d`, `custom`).","schema":{"type":"string"}},{"name":"group_by","in":"query","required":false,"description":"Optional grouping key.","schema":{"type":"string"}},{"name":"billable","in":"query","required":false,"description":"`all`, `billable` or `non_billable`.","schema":{"type":"string"}},{"name":"only_overdue","in":"query","required":false,"description":"AR aging: only overdue rows.","schema":{"type":"boolean"}},{"name":"expiring_within","in":"query","required":false,"description":"KYC report: 30, 60 or 90 days.","schema":{"type":"integer"}},{"name":"reference_date","in":"query","required":false,"description":"As-of date (YYYY-MM-DD).","schema":{"type":"string"}}],"responses":{"200":{"description":"Computed report","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Report"}}}},"401":{"description":"Missing or invalid bearer token","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing `reports:read`","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Forbidden"}}}},"404":{"description":"Unknown report type","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Invalid filters","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"429":{"description":"Too many requests. Honour `Retry-After`.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/webhooks":{"get":{"tags":["Webhooks"],"summary":"List webhook endpoints","description":"Requires `webhooks:manage`. Not paginated. Secrets are never listed.","responses":{"200":{"description":"OK","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/WebhookEndpoint"}}}}}}},"401":{"description":"Missing or invalid bearer token","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing `webhooks:manage`","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Forbidden"}}}},"429":{"description":"Too many requests. Honour `Retry-After`.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Webhooks"],"summary":"Create a webhook endpoint","description":"Requires `webhooks:manage`. The signing secret is returned once in `secret`. URL must be `https://`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpointWrite"}}}},"responses":{"201":{"description":"Created. Copy `secret` now.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpointCreated"}}}},"401":{"description":"Missing or invalid bearer token","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing `webhooks:manage`","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Forbidden"}}}},"422":{"description":"Invalid payload","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"429":{"description":"Too many requests. Honour `Retry-After`.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/webhooks/{webhook}":{"get":{"tags":["Webhooks"],"summary":"Read a webhook endpoint","description":"Requires `webhooks:manage`.","parameters":[{"name":"webhook","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/WebhookEndpoint"}}}}}},"401":{"description":"Missing or invalid bearer token","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing `webhooks:manage`","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Forbidden"}}}},"404":{"description":"No such endpoint in this organisation","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. Honour `Retry-After`.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Webhooks"],"summary":"Update a webhook endpoint","description":"Requires `webhooks:manage`. Does not rotate the secret.","parameters":[{"name":"webhook","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpointWrite"}}}},"responses":{"200":{"description":"OK","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/WebhookEndpoint"}}}}}},"401":{"description":"Missing or invalid bearer token","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing `webhooks:manage`","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Forbidden"}}}},"404":{"description":"No such endpoint in this organisation","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Invalid payload","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"429":{"description":"Too many requests. Honour `Retry-After`.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Webhooks"],"summary":"Delete a webhook endpoint","description":"Requires `webhooks:manage`.","parameters":[{"name":"webhook","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"Deleted","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}}},"401":{"description":"Missing or invalid bearer token","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing `webhooks:manage`","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Forbidden"}}}},"404":{"description":"No such endpoint in this organisation","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. Honour `Retry-After`.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/openapi.json":{"get":{"summary":"This document","security":[],"responses":{"200":{"description":"OpenAPI 3.1 document"}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"lev_live_","description":"Organisation API key minted under Settings \u2192 API-n\u00f8gler. Production keys start with `lev_live_`; non-production with `lev_test_`. Scopes: `matters:read`, `matters:write`, `clients:read`, `clients:write`, `parties:write`, `time:read`, `time:write`, `invoices:read`, `reports:read`, `documents:read`, `settings:read`, `settings:write`, `webhooks:manage`."}},"headers":{"XRateLimitLimit":{"description":"Requests allowed per minute for this API client.","schema":{"type":"integer"}},"XRateLimitRemaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"XRateLimitReset":{"description":"Unix timestamp when the window resets.","schema":{"type":"integer"}},"RetryAfter":{"description":"Seconds to wait before retrying a 429.","schema":{"type":"integer"}}},"schemas":{"Error":{"type":"object","required":["message"],"properties":{"message":{"type":"string","examples":["Unauthenticated.","Not found.","Too Many Attempts."]}}},"Forbidden":{"type":"object","required":["message","required_scope"],"properties":{"message":{"type":"string","examples":["Insufficient scope."]},"required_scope":{"type":"string","enum":["matters:read","matters:write","clients:read","clients:write","parties:write","time:read","time:write","invoices:read","reports:read","documents:read","settings:read","settings:write","webhooks:manage"]}}},"ValidationError":{"type":"object","required":["message"],"properties":{"message":{"type":"string"},"errors":{"type":"object","additionalProperties":{"type":"array","items":{"type":"string"}}}}},"CursorMeta":{"type":"object","required":["per_page"],"properties":{"per_page":{"type":"integer"}}},"CursorLinks":{"type":"object","properties":{"next":{"type":["string","null"],"format":"uri","description":"Absolute URL of the next page, or null when finished."}}},"Matter":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"matter_number":{"type":["string","null"]},"title":{"type":"string"},"description":{"type":["string","null"]},"status":{"type":"string","enum":["active","dormant","closed","archived"]},"practice_area":{"type":["string","null"],"enum":["civil","criminal","corporate","family","real_estate","ip","employment","tax","immigration","administrative","other",null]},"billing_method":{"type":["string","null"],"enum":["hourly","fixed_fee","success_fee","retainer","pro_bono",null]},"hourly_rate":{"type":"integer","description":"Hourly rate. Integer \u00f8re (minor units), never a decimal."},"fixed_fee_amount":{"type":"integer","description":"Fixed fee. Integer \u00f8re (minor units), never a decimal."},"budget_amount":{"type":"integer","description":"Budget. Integer \u00f8re (minor units), never a decimal."},"currency":{"type":"string","examples":["DKK"]},"client_id":{"type":["string","null"],"format":"uuid"},"responsible_attorney_id":{"type":["string","null"],"format":"uuid"},"reference":{"type":["string","null"]},"court_case_number":{"type":["string","null"]},"opposing_party":{"type":["string","null"]},"opened_at":{"type":["string","null"],"format":"date-time"},"closed_at":{"type":["string","null"],"format":"date-time"},"created_at":{"type":["string","null"],"format":"date-time"},"updated_at":{"type":["string","null"],"format":"date-time"}}},"Client":{"type":"object","description":"Engagement. Stamdata is resolved from the party (company or person). CPR and notes are not exposed.","properties":{"id":{"type":"string","format":"uuid"},"type":{"type":"string","enum":["company","person"]},"name":{"type":["string","null"]},"email":{"type":["string","null"]},"phone":{"type":["string","null"]},"address":{"type":["string","null"]},"zip":{"type":["string","null"]},"city":{"type":["string","null"]},"country":{"type":["string","null"]},"cvr":{"type":["string","null"]},"vat_number":{"type":["string","null"]},"company_id":{"type":["string","null"],"format":"uuid"},"person_id":{"type":["string","null"],"format":"uuid"},"kyc_status":{"type":["string","null"]},"risk_level":{"type":["string","null"]},"kyc_verified_at":{"type":["string","null"],"format":"date-time"},"kyc_next_review_date":{"type":["string","null"],"format":"date"},"kyc_monitoring_enabled":{"type":["boolean","null"]},"economic_customer_number":{"type":["string","null"]},"relationship_ended_at":{"type":["string","null"],"format":"date-time"},"created_at":{"type":["string","null"],"format":"date-time"},"updated_at":{"type":["string","null"],"format":"date-time"}}},"Person":{"type":"object","description":"A person party. Resolve a client engagement's `person_id` here. CPR is never exposed, in plaintext or as a hash, and neither are notes or photos.","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":["string","null"]},"email":{"type":["string","null"]},"phone":{"type":["string","null"]},"address":{"type":["string","null"]},"zip":{"type":["string","null"]},"city":{"type":["string","null"]},"country":{"type":["string","null"]},"created_at":{"type":["string","null"],"format":"date-time"},"updated_at":{"type":["string","null"],"format":"date-time"}}},"Company":{"type":"object","description":"A company party. Resolve a client engagement's `company_id` here. One company may hold several engagements. Notes, the CVR register snapshot and logos stay internal.","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":["string","null"]},"email":{"type":["string","null"]},"phone":{"type":["string","null"]},"address":{"type":["string","null"]},"zip":{"type":["string","null"]},"city":{"type":["string","null"]},"country":{"type":["string","null"]},"cvr":{"type":["string","null"]},"vat_number":{"type":["string","null"]},"created_at":{"type":["string","null"],"format":"date-time"},"updated_at":{"type":["string","null"],"format":"date-time"}}},"TimeEntry":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"matter_id":{"type":["string","null"],"format":"uuid"},"user_id":{"type":["string","null"],"format":"uuid"},"date":{"type":["string","null"],"format":"date"},"duration_minutes":{"type":["integer","null"]},"internal_duration_minutes":{"type":["integer","null"]},"activity_code":{"type":["string","null"]},"description":{"type":["string","null"]},"is_billable":{"type":"boolean"},"status":{"type":"string","enum":["draft","submitted","approved","invoiced","written_off"]},"hourly_rate":{"type":"integer","description":"Billable hourly rate. Integer \u00f8re (minor units), never a decimal."},"internal_rate":{"type":"integer","description":"Internal rate. Integer \u00f8re (minor units), never a decimal."},"amount":{"type":"integer","description":"Billable amount. Integer \u00f8re (minor units), never a decimal."},"internal_amount":{"type":"integer","description":"Internal amount. Integer \u00f8re (minor units), never a decimal."},"currency":{"type":"string","examples":["DKK"]},"approved_by":{"type":["string","null"],"format":"uuid"},"approved_at":{"type":["string","null"],"format":"date-time"},"submitted_at":{"type":["string","null"],"format":"date-time"},"written_off_by":{"type":["string","null"],"format":"uuid"},"written_off_at":{"type":["string","null"],"format":"date-time"},"write_off_reason":{"type":["string","null"]},"created_at":{"type":["string","null"],"format":"date-time"},"updated_at":{"type":["string","null"],"format":"date-time"}}},"Invoice":{"type":"object","description":"`lines` is present on show, omitted on the index. Encrypted notes stay internal.","properties":{"id":{"type":"string","format":"uuid"},"invoice_number":{"type":["string","null"]},"status":{"type":"string","enum":["draft","approved","sent","partially_paid","overdue","paid","cancelled","credited"]},"billing_method":{"type":["string","null"]},"matter_id":{"type":["string","null"],"format":"uuid"},"client_id":{"type":["string","null"],"format":"uuid"},"subtotal":{"type":"integer","description":"Net amount. Integer \u00f8re (minor units), never a decimal."},"vat_amount":{"type":"integer","description":"VAT. Integer \u00f8re (minor units), never a decimal."},"total":{"type":"integer","description":"Gross total. Integer \u00f8re (minor units), never a decimal."},"paid_amount":{"type":"integer","description":"Sum of payments. Integer \u00f8re (minor units), never a decimal."},"outstanding":{"type":"integer","description":"Still owed, clamped at zero. Integer \u00f8re (minor units), never a decimal."},"vat_rate":{"type":"number","description":"Decimal rate, e.g. 0.25."},"vat_zone":{"type":"string","enum":["home_country","eu_reverse_charge","eu_consumer","abroad"]},"currency":{"type":"string","examples":["DKK"]},"issue_date":{"type":["string","null"],"format":"date"},"due_date":{"type":["string","null"],"format":"date"},"paid_at":{"type":["string","null"],"format":"date"},"reference":{"type":["string","null"]},"created_by":{"type":["string","null"],"format":"uuid"},"approved_by":{"type":["string","null"],"format":"uuid"},"approved_at":{"type":["string","null"],"format":"date-time"},"sent_at":{"type":["string","null"],"format":"date-time"},"credited_by_invoice_id":{"type":["string","null"],"format":"uuid"},"reminders_sent_count":{"type":"integer"},"last_reminder_sent_at":{"type":["string","null"],"format":"date-time"},"collection_status":{"type":["string","null"]},"procesrente_amount":{"type":"integer","description":"Statutory interest. Integer \u00f8re (minor units), never a decimal."},"lines":{"type":"array","items":{"$ref":"#/components/schemas/InvoiceLine"}},"created_at":{"type":["string","null"],"format":"date-time"},"updated_at":{"type":["string","null"],"format":"date-time"}}},"InvoiceLine":{"type":"object","description":"Amounts are integer \u00f8re. Currency lives on the parent invoice.","properties":{"id":{"type":"string","format":"uuid"},"time_entry_id":{"type":["string","null"],"format":"uuid"},"description":{"type":["string","null"]},"quantity":{"type":"number"},"unit_price":{"type":"integer","description":"Unit price. Integer \u00f8re (minor units), never a decimal."},"amount":{"type":"integer","description":"Line total. Integer \u00f8re (minor units), never a decimal."},"is_time_based":{"type":"boolean"},"source":{"type":["string","null"],"enum":["time","outlay","manual",null]},"is_vat_exempt":{"type":"boolean"},"sort_order":{"type":"integer"}}},"Document":{"type":"object","description":"Document metadata. Bytes, download URLs, previews, extracted text and AI analysis are deliberately not part of v1: enough to reconcile which files exist, not to move privileged material off the platform.","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string","description":"Display name in the DMS."},"filename":{"type":"string","description":"Original filename as uploaded."},"mime":{"type":"string","examples":["application/pdf"]},"size_bytes":{"type":"integer","description":"File size in bytes."},"matter_id":{"type":["string","null"],"format":"uuid"},"folder_id":{"type":["string","null"],"format":"uuid"},"created_at":{"type":["string","null"],"format":"date-time"},"updated_at":{"type":["string","null"],"format":"date-time"}}},"Report":{"type":"object","required":["data","meta"],"properties":{"data":{"type":"object","additionalProperties":true,"description":"Shape depends on `{type}`. Same payload the in-app report computes."},"meta":{"type":"object","properties":{"type":{"type":"string","enum":["revenue","time_distribution","matter_status","productivity","ar_aging","wip","write_off","lead_conversion","kyc_status"]},"currency":{"type":"string","examples":["DKK"]}}}}},"CompanyWrite":{"type":"object","description":"Stamdata for a company party. Creating one does not open an engagement; POST `/clients` afterwards for that.","required":["name"],"properties":{"name":{"type":"string","maxLength":255},"email":{"type":["string","null"],"format":"email","maxLength":255},"phone":{"type":["string","null"],"maxLength":50},"website":{"type":["string","null"],"maxLength":255,"description":"Normalised on save, and used to fetch the logo in the background."},"address":{"type":["string","null"],"maxLength":255},"zip":{"type":["string","null"],"maxLength":20},"city":{"type":["string","null"],"maxLength":255},"country":{"type":["string","null"],"minLength":2,"maxLength":2,"description":"ISO 3166-1 alpha-2. Defaults to `DK`.","examples":["DK"]},"cvr":{"type":["string","null"],"maxLength":20,"description":"Eight digits for a Danish company. Must be unique within the organisation; a duplicate is a `422` naming the company that holds it. A CVR here triggers the same background register lookup the app does."},"registration_scheme":{"type":["string","null"],"enum":["cvr","lei","national"],"description":"How the company is registered. Defaults to the Danish CVR scheme."},"registration_number":{"type":["string","null"],"maxLength":64,"description":"The identifier under a non-CVR scheme. A foreign scheme also requires `country`."},"vat_number":{"type":["string","null"],"maxLength":20,"description":"Ignored for a Danish company: `DK` + the CVR is derived, not stored twice."},"notes":{"type":["string","null"],"maxLength":5000,"description":"Encrypted at rest and readable in the app. Never returned by the API."}}},"PersonWrite":{"type":"object","description":"Stamdata for a person party. Creating one does not open an engagement; POST `/clients` afterwards for that. `cpr` is not merely absent from this schema: sending it is a `422`.","required":["name"],"properties":{"name":{"type":"string","maxLength":255},"email":{"type":["string","null"],"format":"email","maxLength":255,"description":"Must be unique within the organisation; a duplicate is a `422` naming the person who holds it."},"phone":{"type":["string","null"],"maxLength":50},"linkedin_url":{"type":["string","null"],"maxLength":255},"address":{"type":["string","null"],"maxLength":255},"zip":{"type":["string","null"],"maxLength":20},"city":{"type":["string","null"],"maxLength":255},"country":{"type":["string","null"],"minLength":2,"maxLength":2,"description":"ISO 3166-1 alpha-2. Defaults to `DK`.","examples":["DK"]},"notes":{"type":["string","null"],"maxLength":5000,"description":"Encrypted at rest and readable in the app. Never returned by the API."}}},"ClientWrite":{"type":"object","description":"An engagement on a party that already exists. Send exactly one of `company_id` or `person_id`. The engagement holds no stamdata of its own: name, address, CVR and CPR stay on the party. KYC starts at `pending` for every imported engagement, because a status the firm has not reached is not one an importer may assert.","properties":{"company_id":{"type":["string","null"],"format":"uuid","description":"From `POST /companies` or `GET /companies`. Required when `person_id` is absent, and prohibited when it is present."},"person_id":{"type":["string","null"],"format":"uuid","description":"From `POST /people` or `GET /people`. Required when `company_id` is absent."},"risk_level":{"type":["string","null"],"enum":["low","medium","high"],"description":"Opening risk assessment carried over from the old system. Defaults to `low`. `prohibited` is a verdict the firm reaches in Levano, not an opening position, so it is not accepted here."},"notes":{"type":["string","null"],"maxLength":5000,"description":"Engagement notes. Readable in the app; never returned by the API."}}},"MatterWrite":{"type":"object","description":"A sag on an engagement that already exists. `status` opens at `active`, and hvidvasklovens section 14 applies to that exactly as it does in the app: a matter cannot open active while the client's KYC case is missing, unapproved or expired. Import historical matters as `dormant`, `closed` or `archived`, or approve the KYC first. The section 14(2) exemption is not available over the API, because it is an audited decision by a named person.","required":["client_id","title"],"properties":{"client_id":{"type":"string","format":"uuid","description":"From `POST /clients` or `GET /clients`. An id from another organisation is a `422`."},"title":{"type":"string","maxLength":255},"matter_number":{"type":["string","null"],"maxLength":255,"description":"The firm's own sagsnummer, kept as sent. Omit it and Levano assigns the next number in its `SAG-0001` series. Supply it when migrating, because the old number is what the firm's letters and bogholderi refer to, and there is no PATCH to set it afterwards. Must be unique within the organisation.","examples":["2019-0442"]},"description":{"type":["string","null"],"maxLength":5000},"status":{"type":["string","null"],"enum":["active","dormant","closed","archived"],"description":"Defaults to `active`. See the KYC note above before importing as `active`."},"practice_area":{"type":["string","null"],"enum":["civil","criminal","corporate","family","real_estate","ip","employment","tax","immigration","administrative","other"]},"billing_method":{"type":["string","null"],"enum":["hourly","fixed_fee","success_fee","retainer","pro_bono"],"description":"Defaults to `hourly`."},"hourly_rate":{"type":["integer","null"],"minimum":0,"description":"Rate in \u00f8re. A time entry on this matter that carries no rate of its own inherits this one. Integer minor units: 125000 is 1.250,00 kr. A decimal is a `422`, including `1250.00`, because a body that carries kroner where the contract says \u00f8re is off by a factor of a hundred."},"fixed_fee_amount":{"type":["integer","null"],"minimum":0,"description":"Fixed fee in \u00f8re. Integer minor units: 125000 is 1.250,00 kr. A decimal is a `422`, including `1250.00`, because a body that carries kroner where the contract says \u00f8re is off by a factor of a hundred."},"budget_amount":{"type":["integer","null"],"minimum":0,"description":"Budget in \u00f8re. Levano sends budget alerts against it as hours land. Integer minor units: 125000 is 1.250,00 kr. A decimal is a `422`, including `1250.00`, because a body that carries kroner where the contract says \u00f8re is off by a factor of a hundred."},"responsible_attorney_id":{"type":["string","null"],"format":"uuid","description":"Must be a user in this organisation. A user id from another firm is a `422`, never a stranger on the sag."},"reference":{"type":["string","null"],"maxLength":255},"court_case_number":{"type":["string","null"],"maxLength":255},"opposing_party":{"type":["string","null"],"maxLength":255},"opened_at":{"type":["string","null"],"format":"date-time"},"closed_at":{"type":["string","null"],"format":"date-time","description":"Only accepted when `status` is `closed` or `archived`. The create path does not stamp it, so a historical sag has to carry its own closing date or lose it."},"notes":{"type":["string","null"],"maxLength":5000,"description":"Internal notes. Readable in the app; never returned by the API."}}},"TimeEntryWrite":{"type":"object","description":"An hour on a matter that already exists. `amount` is not in this schema and sending it is a `422`: Levano derives it from `duration_minutes` and `hourly_rate` on every save, so a value sent here would be silently recomputed. `status` is refused for the same reason it is refused on a client: every imported entry opens at `draft`, and `invoiced` means a Levano invoice line points at the entry.","required":["matter_id","user_id","date","duration_minutes"],"properties":{"matter_id":{"type":"string","format":"uuid","description":"From `POST /matters` or `GET /matters`. Required: there is no timer over the API, so there is no orphan hour either."},"user_id":{"type":"string","format":"uuid","description":"Who did the work. Must be a member of this organisation; `GET /reports/time-distribution` is one way to see the ids you already have. A user from another firm is a `422`."},"date":{"type":"string","format":"date","examples":["2026-03-14"]},"duration_minutes":{"type":"integer","minimum":1,"description":"Minutes, not hours and not \u00f8re. Zero is refused: an entry of no minutes is noise in every report that sums the register."},"description":{"type":["string","null"],"maxLength":1000},"activity_code":{"type":["string","null"],"description":"The `code` of an active activity code in this organisation. Create the codes in Levano first; an unknown one is a `422`."},"is_billable":{"type":["boolean","null"],"description":"Defaults to `true`."},"hourly_rate":{"type":["integer","null"],"minimum":0,"description":"Rate in \u00f8re. Omit it and the matter's own `hourly_rate` is used; if the matter has none either, the entry lands without an amount. Integer minor units: 125000 is 1.250,00 kr. A decimal is a `422`, including `1250.00`, because a body that carries kroner where the contract says \u00f8re is off by a factor of a hundred."},"internal_duration_minutes":{"type":["integer","null"],"minimum":0,"description":"Internal cost minutes when they differ from the billed ones."},"internal_rate":{"type":["integer","null"],"minimum":0,"description":"Internal cost rate in \u00f8re. Integer minor units: 125000 is 1.250,00 kr. A decimal is a `422`, including `1250.00`, because a body that carries kroner where the contract says \u00f8re is off by a factor of a hundred."}}},"WebhookEndpoint":{"type":"object","description":"The signing secret is never included after create.","properties":{"id":{"type":"string","format":"uuid"},"url":{"type":"string","format":"uri"},"event_types":{"type":"array","items":{"type":"string","enum":["matter.created","matter.closed","invoice.issued","invoice.paid","document.signed","time_entry.created"]}},"is_active":{"type":"boolean"},"consecutive_failures":{"type":"integer"},"last_delivery_at":{"type":["string","null"],"format":"date-time"},"created_at":{"type":["string","null"],"format":"date-time"},"updated_at":{"type":["string","null"],"format":"date-time"}}},"WebhookEndpointWrite":{"type":"object","properties":{"url":{"type":"string","format":"uri","description":"Must start with `https://`. Required on create."},"event_types":{"type":"array","minItems":1,"items":{"type":"string","enum":["matter.created","matter.closed","invoice.issued","invoice.paid","document.signed","time_entry.created"]},"description":"Required on create."},"is_active":{"type":"boolean"}}},"WebhookEndpointCreated":{"type":"object","required":["data","secret"],"properties":{"data":{"$ref":"#/components/schemas/WebhookEndpoint"},"secret":{"type":"string","description":"Plaintext signing secret. Shown once."}}},"WebhookEvent":{"type":"object","description":"POSTed to every matching endpoint. Verify `Levano-Signature: t=<unix>,v1=<hex>` as HMAC-SHA256 of `{t}.{raw body}` using the endpoint secret before trusting anything in it. Deliveries are retried with exponential backoff (five attempts) and may arrive more than once, so deduplicate on `id`. After 20 consecutive failures the endpoint is disabled (`is_active = false`). `data` carries identifiers and status only; fetch the rest from the API with your own token.","required":["id","event","timestamp","organization_id","data"],"properties":{"id":{"type":"string","format":"uuid"},"event":{"type":"string","enum":["matter.created","matter.closed","invoice.issued","invoice.paid","document.signed","time_entry.created"]},"timestamp":{"type":"string","format":"date-time"},"organization_id":{"type":"string","format":"uuid"},"data":{"type":"object","additionalProperties":true,"description":"Event-specific identifiers. `matter.created` / `matter.closed` \u2192 `matter`. `invoice.issued` / `invoice.paid` \u2192 `invoice` (amounts in \u00f8re). `time_entry.created` \u2192 `time_entry`. `document.signed` \u2192 `signing_flow`."}}}}},"webhooks":{"matter.created":{"post":{"summary":"A matter was opened.","security":[],"parameters":[{"name":"Levano-Signature","in":"header","required":true,"description":"`t=<unix>,v1=<hex>`. HMAC-SHA256 of `{t}.{raw body}` using the endpoint secret.","schema":{"type":"string"}},{"name":"Levano-Event-Id","in":"header","required":true,"description":"Stable across retries of the same event. Deduplicate on this.","schema":{"type":"string","format":"uuid"}},{"name":"Levano-Event-Type","in":"header","required":true,"schema":{"type":"string","enum":["matter.created"]}},{"name":"Levano-Delivery-Attempt","in":"header","schema":{"type":"integer","minimum":1}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"2XX":{"description":"Any 2xx marks the delivery succeeded. Anything else is retried."}}}},"matter.closed":{"post":{"summary":"A matter was closed.","security":[],"parameters":[{"name":"Levano-Signature","in":"header","required":true,"description":"`t=<unix>,v1=<hex>`. HMAC-SHA256 of `{t}.{raw body}` using the endpoint secret.","schema":{"type":"string"}},{"name":"Levano-Event-Id","in":"header","required":true,"description":"Stable across retries of the same event. Deduplicate on this.","schema":{"type":"string","format":"uuid"}},{"name":"Levano-Event-Type","in":"header","required":true,"schema":{"type":"string","enum":["matter.closed"]}},{"name":"Levano-Delivery-Attempt","in":"header","schema":{"type":"integer","minimum":1}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"2XX":{"description":"Any 2xx marks the delivery succeeded. Anything else is retried."}}}},"invoice.issued":{"post":{"summary":"An invoice was issued / sent.","security":[],"parameters":[{"name":"Levano-Signature","in":"header","required":true,"description":"`t=<unix>,v1=<hex>`. HMAC-SHA256 of `{t}.{raw body}` using the endpoint secret.","schema":{"type":"string"}},{"name":"Levano-Event-Id","in":"header","required":true,"description":"Stable across retries of the same event. Deduplicate on this.","schema":{"type":"string","format":"uuid"}},{"name":"Levano-Event-Type","in":"header","required":true,"schema":{"type":"string","enum":["invoice.issued"]}},{"name":"Levano-Delivery-Attempt","in":"header","schema":{"type":"integer","minimum":1}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"2XX":{"description":"Any 2xx marks the delivery succeeded. Anything else is retried."}}}},"invoice.paid":{"post":{"summary":"An invoice was paid in full.","security":[],"parameters":[{"name":"Levano-Signature","in":"header","required":true,"description":"`t=<unix>,v1=<hex>`. HMAC-SHA256 of `{t}.{raw body}` using the endpoint secret.","schema":{"type":"string"}},{"name":"Levano-Event-Id","in":"header","required":true,"description":"Stable across retries of the same event. Deduplicate on this.","schema":{"type":"string","format":"uuid"}},{"name":"Levano-Event-Type","in":"header","required":true,"schema":{"type":"string","enum":["invoice.paid"]}},{"name":"Levano-Delivery-Attempt","in":"header","schema":{"type":"integer","minimum":1}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"2XX":{"description":"Any 2xx marks the delivery succeeded. Anything else is retried."}}}},"document.signed":{"post":{"summary":"A signing flow was sealed.","security":[],"parameters":[{"name":"Levano-Signature","in":"header","required":true,"description":"`t=<unix>,v1=<hex>`. HMAC-SHA256 of `{t}.{raw body}` using the endpoint secret.","schema":{"type":"string"}},{"name":"Levano-Event-Id","in":"header","required":true,"description":"Stable across retries of the same event. Deduplicate on this.","schema":{"type":"string","format":"uuid"}},{"name":"Levano-Event-Type","in":"header","required":true,"schema":{"type":"string","enum":["document.signed"]}},{"name":"Levano-Delivery-Attempt","in":"header","schema":{"type":"integer","minimum":1}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"2XX":{"description":"Any 2xx marks the delivery succeeded. Anything else is retried."}}}},"time_entry.created":{"post":{"summary":"A time entry was created.","security":[],"parameters":[{"name":"Levano-Signature","in":"header","required":true,"description":"`t=<unix>,v1=<hex>`. HMAC-SHA256 of `{t}.{raw body}` using the endpoint secret.","schema":{"type":"string"}},{"name":"Levano-Event-Id","in":"header","required":true,"description":"Stable across retries of the same event. Deduplicate on this.","schema":{"type":"string","format":"uuid"}},{"name":"Levano-Event-Type","in":"header","required":true,"schema":{"type":"string","enum":["time_entry.created"]}},{"name":"Levano-Delivery-Attempt","in":"header","schema":{"type":"integer","minimum":1}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"2XX":{"description":"Any 2xx marks the delivery succeeded. Anything else is retried."}}}}}}