{
    "openapi": "3.1.0",
    "info": {
        "title": "Ledgr API",
        "version": "v1",
        "description": "The Ledgr API is the same API the Ledgr apps use — there is no reduced\n\"integration\" surface, and no endpoint the product uses that you cannot.\n\n## Authenticating\n\n1. The customer creates an API key in **Settings → Roles → API keys**, choosing\n   its scopes, and gives it to you. They can revoke it at any time, and a\n   revoked key stops working on the next request rather than when its last\n   token expires.\n2. `POST /auth/token` with `Authorization: Bearer <key>` returns an access token\n   valid for one hour.\n3. Send that token as `Authorization: Bearer <token>` on every other call.\n\nYou never need a customer's password. If an integration asks for one, that is\na reason to refuse it.\n\n## What a key can do\n\nThree limits apply together, and the narrowest wins:\n\n- **The tenant's plan.** An endpoint in an unlicensed module returns\n  `403 MODULE_NOT_LICENSED` regardless of scopes.\n- **The key's scopes.** `403 INSUFFICIENT_SCOPE`, naming the scope needed.\n- **The role of the user who created the key.** A key made by a Viewer cannot\n  write, whatever scopes it was given.\n\n## Rate limits\n\n120 requests per minute per key by default. Every response carries\n`X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset`, so you can\npace yourself without probing for the ceiling. Over the limit returns `429` with\n`Retry-After`.\n\n## Multi-company tenants\n\nA tenant may hold several companies. Send `X-Company-Id: <uuid>` to act within\none; `GET /api/companies` lists them. Company-scoped endpoints reject a request\nwith no company when the tenant has more than one, rather than picking for you.\n\n## Money and dates\n\nAmounts are decimal numbers in the document's currency, ZAR unless stated.\nDates are `YYYY-MM-DD`. Timestamps are ISO-8601 with an offset, in UTC.",
        "contact": {
            "name": "Ledgr developer support",
            "url": "https://ledgr.co.za/developers/"
        }
    },
    "servers": [
        {
            "url": "https://api.ledgr.co.za",
            "description": "Production"
        }
    ],
    "tags": [
        {
            "name": "Authentication"
        },
        {
            "name": "Two-factor authentication"
        },
        {
            "name": "Tenant, plan & entitlements"
        },
        {
            "name": "Billing & subscription"
        },
        {
            "name": "Users, roles & security"
        },
        {
            "name": "API keys"
        },
        {
            "name": "Files & attachments"
        },
        {
            "name": "Search, insights & deadlines"
        },
        {
            "name": "Push notifications"
        },
        {
            "name": "Contacts"
        },
        {
            "name": "Invoicing & quotes"
        },
        {
            "name": "Payments & gateways"
        },
        {
            "name": "Customer portal"
        },
        {
            "name": "Sales CRM"
        },
        {
            "name": "Expenses & claims"
        },
        {
            "name": "Purchasing & suppliers"
        },
        {
            "name": "Banking & reconciliation"
        },
        {
            "name": "General ledger"
        },
        {
            "name": "Reports & analytics"
        },
        {
            "name": "SARS returns"
        },
        {
            "name": "Payroll"
        },
        {
            "name": "Leave"
        },
        {
            "name": "Employee self-service portal"
        },
        {
            "name": "Inventory"
        },
        {
            "name": "Job cards"
        },
        {
            "name": "Service contracts"
        },
        {
            "name": "Time tracking"
        },
        {
            "name": "Workflows"
        },
        {
            "name": "Bills of materials"
        },
        {
            "name": "Material planning (MRP)"
        },
        {
            "name": "Production & work orders"
        },
        {
            "name": "Procurement"
        },
        {
            "name": "Supplier portal API"
        },
        {
            "name": "Supplier workspace, buyer side"
        },
        {
            "name": "Logistics & fleet"
        },
        {
            "name": "Driver app API"
        },
        {
            "name": "Multi-company & consolidation"
        },
        {
            "name": "Accountant workspace"
        },
        {
            "name": "Migration & take-on"
        },
        {
            "name": "Offline sync"
        },
        {
            "name": "WhatsApp"
        },
        {
            "name": "External ingest (API key)"
        },
        {
            "name": "Inbound webhooks"
        },
        {
            "name": "Platform administration"
        },
        {
            "name": "Operations & discovery"
        }
    ],
    "components": {
        "securitySchemes": {
            "apiKey": {
                "type": "http",
                "scheme": "bearer",
                "description": "A Ledgr API key (ledgr_…), used ONLY at POST /auth/token to obtain an access token. Keys are created in Settings → Roles → API keys and can be revoked there at any time."
            },
            "bearerAuth": {
                "type": "http",
                "scheme": "bearer",
                "bearerFormat": "JWT",
                "description": "An access token from POST /auth/token (integrations) or POST /auth/login (first-party clients). Send as: Authorization: Bearer <token>."
            }
        },
        "schemas": {
            "Error": {
                "type": "object",
                "properties": {
                    "error": {
                        "type": "string"
                    },
                    "message": {
                        "type": "string"
                    }
                }
            }
        },
        "responses": {
            "Unauthorized": {
                "description": "Missing, malformed or expired access token.",
                "content": {
                    "application/json": {
                        "schema": {
                            "$ref": "#/components/schemas/Error"
                        }
                    }
                }
            },
            "Forbidden": {
                "description": "The token is valid but the call is not permitted. `MODULE_NOT_LICENSED` — the tenant's plan does not include this module. `INSUFFICIENT_SCOPE` — the API key lacks the named scope. `LICENSE_EXPIRED` — reads still work, writes do not.",
                "content": {
                    "application/json": {
                        "schema": {
                            "$ref": "#/components/schemas/Error"
                        }
                    }
                }
            },
            "RateLimited": {
                "description": "The key exceeded its per-minute ceiling. `Retry-After` gives the seconds until the window rolls; `X-RateLimit-*` headers are on every response, not only this one.",
                "headers": {
                    "Retry-After": {
                        "description": "Seconds until the window resets.",
                        "schema": {
                            "type": "integer"
                        }
                    }
                },
                "content": {
                    "application/json": {
                        "schema": {
                            "$ref": "#/components/schemas/Error"
                        }
                    }
                }
            }
        }
    },
    "security": [
        {
            "bearerAuth": []
        }
    ],
    "paths": {
        "/auth/register": {
            "post": {
                "tags": [
                    "Authentication"
                ],
                "summary": "Create a tenant and its first user. Returns a JWT.",
                "operationId": "postAuthRegister",
                "security": [],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "tenantName": {
                                        "type": "string"
                                    },
                                    "email": {
                                        "type": "string"
                                    },
                                    "password": {
                                        "type": "string"
                                    },
                                    "firstName": {
                                        "type": "string"
                                    },
                                    "lastName": {
                                        "type": "string"
                                    },
                                    "licenseCode": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "tenantName": "Cape Fine Foods",
                                "email": "andy@example.co.za",
                                "password": "2e1167ef9d17bf3d4bb558dc6a88e9b822d298a8837447f6d9ac3de7e592d909",
                                "firstName": "Andy",
                                "lastName": "Cameron",
                                "licenseCode": ""
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "token": {
                                            "type": "string"
                                        },
                                        "userId": {
                                            "type": "string"
                                        },
                                        "tenantId": {
                                            "type": "string"
                                        },
                                        "email": {
                                            "type": "string"
                                        },
                                        "tenantName": {
                                            "type": "string"
                                        }
                                    }
                                },
                                "example": {
                                    "token": "<jwt>",
                                    "userId": "<uuid>",
                                    "tenantId": "<uuid>",
                                    "email": "you@example.co.za",
                                    "tenantName": "Your Business"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/auth/login": {
            "post": {
                "tags": [
                    "Authentication"
                ],
                "summary": "Exchange email and password for a JWT. A TOTP code and a recovery code go in the same field — they are told apart by shape, six digits against Crockford Base32 ABCDE-FGHJK.",
                "operationId": "postAuthLogin",
                "security": [],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "email": {
                                        "type": "string"
                                    },
                                    "password": {
                                        "type": "string"
                                    },
                                    "totpCode": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "email": "you@example.co.za",
                                "password": "your-password",
                                "totpCode": "123456"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "token": {
                                            "type": "string"
                                        },
                                        "refreshToken": {
                                            "type": "string"
                                        },
                                        "userId": {
                                            "type": "string"
                                        },
                                        "tenantId": {
                                            "type": "string"
                                        },
                                        "email": {
                                            "type": "string"
                                        },
                                        "tenantName": {
                                            "type": "string"
                                        }
                                    }
                                },
                                "example": {
                                    "token": "<jwt>",
                                    "refreshToken": "<refresh-token>",
                                    "userId": "<uuid>",
                                    "tenantId": "<uuid>",
                                    "email": "you@example.co.za",
                                    "tenantName": "Your Business"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/auth/refresh": {
            "post": {
                "tags": [
                    "Authentication"
                ],
                "summary": "Exchange a refresh token for a new access token.",
                "operationId": "postAuthRefresh",
                "security": [],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "refreshToken": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "refreshToken": "<refresh-token>"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    }
                }
            }
        },
        "/auth/logout": {
            "post": {
                "tags": [
                    "Authentication"
                ],
                "summary": "Revoke the current session. The token stops working on the next request rather than when it expires.",
                "operationId": "postAuthLogout",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/auth/me": {
            "get": {
                "tags": [
                    "Authentication"
                ],
                "summary": "The signed-in user, their tenant, their roles and the company they are acting in.",
                "operationId": "getAuthMe",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/auth/forgot-password": {
            "post": {
                "tags": [
                    "Authentication"
                ],
                "summary": "Send a password-reset email. Answers identically whether or not the address exists, so it cannot be probed for.",
                "operationId": "postAuthForgotPassword",
                "security": [],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "email": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "email": "you@example.co.za"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    }
                }
            }
        },
        "/auth/reset-password": {
            "post": {
                "tags": [
                    "Authentication"
                ],
                "summary": "Set a new password from the token in the reset email.",
                "operationId": "postAuthResetPassword",
                "security": [],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "token": {
                                        "type": "string"
                                    },
                                    "newPassword": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "token": "<from the emailed link>",
                                "newPassword": "2e1167ef9d17bf3d4bb558dc6a88e9b822d298a8837447f6d9ac3de7e592d909"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    }
                }
            }
        },
        "/auth/validate-license": {
            "post": {
                "tags": [
                    "Authentication"
                ],
                "summary": "Check a licence key without redeeming it — the plan it carries, its seats and its expiry.",
                "operationId": "postAuthValidateLicense",
                "security": [],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "licenseCode": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "licenseCode": "SKU-1001"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    }
                }
            }
        },
        "/auth/token": {
            "post": {
                "tags": [
                    "Authentication"
                ],
                "summary": "Exchange an API key for a short-lived access token.",
                "operationId": "postAuthToken",
                "description": "Send the API key as a bearer credential; receive a one-hour access token carrying the key's scopes. This is how an integration authenticates — it never needs, and must never be given, a user's password. The token is an ordinary access token, so every endpoint below accepts it unchanged.",
                "security": [
                    {
                        "apiKey": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "A token, its lifetime, the granted scopes and the key's rate limit.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "accessToken": {
                                            "type": "string"
                                        },
                                        "tokenType": {
                                            "type": "string"
                                        },
                                        "expiresIn": {
                                            "type": "integer"
                                        },
                                        "scope": {
                                            "type": "string"
                                        },
                                        "tenantId": {
                                            "type": "string",
                                            "format": "uuid"
                                        },
                                        "rateLimitPerMinute": {
                                            "type": "integer"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    }
                }
            }
        },
        "/api-scopes": {
            "get": {
                "tags": [
                    "Authentication"
                ],
                "summary": "List every scope an API key can hold.",
                "operationId": "getApiScopes",
                "description": "Public and unauthenticated, so tooling can render the scope picker without a key.",
                "security": [],
                "responses": {
                    "200": {
                        "description": "The scope catalogue."
                    }
                }
            }
        },
        "/reset-password": {
            "get": {
                "tags": [
                    "Authentication"
                ],
                "summary": "The password-reset page the emailed link opens. HTML, not JSON.",
                "operationId": "getResetPassword",
                "security": [],
                "responses": {
                    "200": {
                        "description": "Success"
                    }
                }
            }
        },
        "/api/2fa/me": {
            "get": {
                "tags": [
                    "Two-factor authentication"
                ],
                "summary": "Whether this user is enrolled, when they enrolled, and how many recovery codes are unused.",
                "operationId": "getApi2faMe",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "delete": {
                "tags": [
                    "Two-factor authentication"
                ],
                "summary": "Disenrol, using the user's own current code.",
                "operationId": "deleteApi2faMe",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/2fa/enroll": {
            "post": {
                "tags": [
                    "Two-factor authentication"
                ],
                "summary": "Start enrolment. Returns the TOTP secret, the otpauth:// URI and ten recovery codes — the codes are shown ONCE and stored hashed, so no endpoint can ever show them again.",
                "operationId": "postApi2faEnroll",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/2fa/verify": {
            "post": {
                "tags": [
                    "Two-factor authentication"
                ],
                "summary": "Confirm enrolment with a code from the authenticator app.",
                "operationId": "postApi2faVerify",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "code": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "code": "123456"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/2fa/recovery-codes": {
            "post": {
                "tags": [
                    "Two-factor authentication"
                ],
                "summary": "Mint a fresh set of ten recovery codes. Every previously issued code stops working.",
                "operationId": "postApi2faRecoveryCodes",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/users/{id}/2fa/reset": {
            "post": {
                "tags": [
                    "Two-factor authentication"
                ],
                "summary": "Clear ANOTHER user's 2FA after a lost phone. Needs roles:edit plus an owner or admin role, refuses id == self, revokes the target's sessions, audits at warning and emails them.",
                "operationId": "postApiUsersById2faReset",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/tenant/entitlements": {
            "get": {
                "tags": [
                    "Tenant, plan & entitlements"
                ],
                "summary": "The plan, its modules, seat and storage allowances, live usage against them, and the licence's expiry. The client calls this at app start and from its nav guard.",
                "operationId": "getApiTenantEntitlements",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/tenant/apply-license": {
            "post": {
                "tags": [
                    "Tenant, plan & entitlements"
                ],
                "summary": "Redeem a licence key against this tenant. A dated key creates a subscription with auto_renew off, so it announces its expiry rather than lapsing in silence.",
                "operationId": "postApiTenantApplyLicense",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "licenseCode": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "licenseCode": "SKU-1001"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/tenant/addons": {
            "post": {
                "tags": [
                    "Tenant, plan & entitlements"
                ],
                "summary": "Buy or resize an add-on. It takes effect immediately and settles on the next renewal — the same contract the storage meter and the payroll band already have. Needs roles:edit, because it changes what the tenant pays. Quantity is at least 1: removing an add-on is a DELETE, since a zero-quantity holding would grant nothing and still bill. Answers with the whole entitlements document, so the caller never re-reads it.",
                "operationId": "postApiTenantAddons",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "addonId": {
                                        "type": "string"
                                    },
                                    "quantity": {
                                        "type": "integer"
                                    }
                                }
                            },
                            "example": {
                                "addonId": "logistics",
                                "quantity": 1
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/tenant/addons/{addonId}": {
            "delete": {
                "tags": [
                    "Tenant, plan & entitlements"
                ],
                "summary": "Stop an add-on. Cancelled rather than deleted: a holding kept for four months of a twelve-month term is part of explaining that term's invoice, and a row that is gone cannot explain a charge the customer is looking at. Answers with the updated entitlements.",
                "operationId": "deleteApiTenantAddonsByAddonId",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "addonId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/plans": {
            "get": {
                "tags": [
                    "Tenant, plan & entitlements"
                ],
                "summary": "The price list as the server holds it — every plan, its price, its seats and its modules. Prices exclude VAT.",
                "operationId": "getApiPlans",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/tenant-settings": {
            "get": {
                "tags": [
                    "Tenant, plan & entitlements"
                ],
                "summary": "The Settings screen's stored blob — company details, fiscal year, VAT profile, regional and notification preferences.",
                "operationId": "getApiTenantSettings",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "put": {
                "tags": [
                    "Tenant, plan & entitlements"
                ],
                "summary": "Replace the settings blob. This REPLACES every section — send the whole object, not just the part you are changing. GET first.",
                "operationId": "putApiTenantSettings",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "taxVat": {
                                        "type": "object",
                                        "properties": {
                                            "isVatRegistered": {
                                                "type": "boolean"
                                            },
                                            "vatRegistrationDate": {
                                                "type": "string",
                                                "format": "date"
                                            },
                                            "standardVatRate": {
                                                "type": "number"
                                            },
                                            "drcEnabled": {
                                                "type": "boolean"
                                            },
                                            "provisionalTaxPayer": {
                                                "type": "boolean"
                                            },
                                            "sarsTaxRefNumber": {
                                                "type": "string"
                                            }
                                        }
                                    },
                                    "branding": {
                                        "type": "object",
                                        "properties": {
                                            "brandColorHex": {
                                                "type": "string"
                                            }
                                        }
                                    },
                                    "localisation": {
                                        "type": "object",
                                        "properties": {
                                            "currency": {
                                                "type": "string"
                                            },
                                            "locale": {
                                                "type": "string"
                                            },
                                            "dateFormat": {
                                                "type": "string"
                                            },
                                            "timeZone": {
                                                "type": "string"
                                            },
                                            "numberFormat": {
                                                "type": "string"
                                            },
                                            "firstDayOfWeek": {
                                                "type": "string"
                                            }
                                        }
                                    },
                                    "integrations": {
                                        "type": "object",
                                        "properties": {
                                            "whatsapp": {
                                                "type": "boolean"
                                            }
                                        }
                                    },
                                    "notifications": {
                                        "type": "object",
                                        "properties": {
                                            "email-overdue-invoice": {
                                                "type": "boolean"
                                            },
                                            "wa-overdue-followup": {
                                                "type": "boolean"
                                            }
                                        }
                                    },
                                    "dataPrefs": {
                                        "type": "object",
                                        "properties": {
                                            "autoBackupEnabled": {
                                                "type": "boolean"
                                            },
                                            "autoBackupFrequency": {
                                                "type": "string"
                                            },
                                            "retentionMonths": {
                                                "type": "integer"
                                            }
                                        }
                                    }
                                }
                            },
                            "example": {
                                "taxVat": {
                                    "isVatRegistered": true,
                                    "vatRegistrationDate": "2019-04-01",
                                    "standardVatRate": 15.0,
                                    "drcEnabled": false,
                                    "provisionalTaxPayer": true,
                                    "sarsTaxRefNumber": "9012345678"
                                },
                                "branding": {
                                    "brandColorHex": "#0D47A1"
                                },
                                "localisation": {
                                    "currency": "ZAR",
                                    "locale": "en-ZA",
                                    "dateFormat": "DD/MM/YYYY",
                                    "timeZone": "Africa/Johannesburg",
                                    "numberFormat": "1 234,56",
                                    "firstDayOfWeek": "Monday"
                                },
                                "integrations": {
                                    "whatsapp": true
                                },
                                "notifications": {
                                    "email-overdue-invoice": true,
                                    "wa-overdue-followup": false
                                },
                                "dataPrefs": {
                                    "autoBackupEnabled": false,
                                    "autoBackupFrequency": "Daily",
                                    "retentionMonths": 84
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/setup-progress": {
            "get": {
                "tags": [
                    "Tenant, plan & entitlements"
                ],
                "summary": "The dashboard checklist. Four of the five steps are detected live from the books rather than trusted from a flag; completion is per tenant, hiding is per user.",
                "operationId": "getApiSetupProgress",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/setup-progress/steps": {
            "post": {
                "tags": [
                    "Tenant, plan & entitlements"
                ],
                "summary": "Mark the one step nothing can detect — that a migration from the previous system has been done.",
                "operationId": "postApiSetupProgressSteps",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "stepKey": {
                                        "type": "string"
                                    },
                                    "complete": {
                                        "type": "boolean"
                                    }
                                }
                            },
                            "example": {
                                "stepKey": "Example",
                                "complete": true
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/setup-progress/hidden": {
            "post": {
                "tags": [
                    "Tenant, plan & entitlements"
                ],
                "summary": "Hide the checklist for the calling user only. One colleague cannot dismiss it for another.",
                "operationId": "postApiSetupProgressHidden",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "hidden": {
                                        "type": "boolean"
                                    }
                                }
                            },
                            "example": {
                                "hidden": true
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/data/export": {
            "post": {
                "tags": [
                    "Tenant, plan & entitlements"
                ],
                "summary": "A ZIP of CSVs — every table this tenant owns. The export is what the retention notices lead with, because the Tax Administration Act asks a business to keep records for five years.",
                "operationId": "postApiDataExport",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/billing/checkout": {
            "post": {
                "tags": [
                    "Billing & subscription"
                ],
                "summary": "Start a purchase or an upgrade. Returns a Yoco hosted-checkout URL to redirect the buyer to.",
                "operationId": "postApiBillingCheckout",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "planId": {
                                        "type": "string"
                                    },
                                    "termMonths": {
                                        "type": "integer"
                                    }
                                }
                            },
                            "example": {
                                "planId": "business",
                                "termMonths": 12
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/billing/orders": {
            "get": {
                "tags": [
                    "Billing & subscription"
                ],
                "summary": "This tenant's licence orders, paid and outstanding.",
                "operationId": "getApiBillingOrders",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/billing/orders/{id}": {
            "get": {
                "tags": [
                    "Billing & subscription"
                ],
                "summary": "One order — its amount, its state and its checkout link if it is still payable.",
                "operationId": "getApiBillingOrdersById",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/billing/subscription": {
            "get": {
                "tags": [
                    "Billing & subscription"
                ],
                "summary": "The subscription: plan, term, current period end, whether it auto-renews, and the retention date if it has ended.",
                "operationId": "getApiBillingSubscription",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/billing/subscription/auto-renew": {
            "post": {
                "tags": [
                    "Billing & subscription"
                ],
                "summary": "Turn renewal on or off. One endpoint for both directions, because two would let them disagree. Cancelling means stop renewing — no refund and no revocation.",
                "operationId": "postApiBillingSubscriptionAutoRenew",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "autoRenew": {
                                        "type": "boolean"
                                    }
                                }
                            },
                            "example": {
                                "autoRenew": false
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/billing/subscription/pay-now": {
            "post": {
                "tags": [
                    "Billing & subscription"
                ],
                "summary": "Settle the outstanding renewal early. A renewal EXTENDS from the existing expiry, so paying early never costs days.",
                "operationId": "postApiBillingSubscriptionPayNow",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/billing/public-checkout": {
            "post": {
                "tags": [
                    "Billing & subscription"
                ],
                "summary": "Buy a licence before signing up. Mints an order and a Yoco checkout without a tenant existing yet.",
                "operationId": "postBillingPublicCheckout",
                "security": [],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "planId": {
                                        "type": "string"
                                    },
                                    "termMonths": {
                                        "type": "integer"
                                    },
                                    "email": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "planId": "starter",
                                "termMonths": 1,
                                "email": "you@example.co.za"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    }
                }
            }
        },
        "/billing/orders/{ref}": {
            "get": {
                "tags": [
                    "Billing & subscription"
                ],
                "summary": "The public state of a pre-signup order, by its reference. What the buyer's return page polls.",
                "operationId": "getBillingOrdersByRef",
                "security": [],
                "parameters": [
                    {
                        "name": "ref",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    }
                }
            }
        },
        "/billing/success": {
            "get": {
                "tags": [
                    "Billing & subscription"
                ],
                "summary": "The buyer's return page after a successful Yoco checkout. HTML.",
                "operationId": "getBillingSuccess",
                "security": [],
                "responses": {
                    "200": {
                        "description": "Success"
                    }
                }
            }
        },
        "/billing/cancelled": {
            "get": {
                "tags": [
                    "Billing & subscription"
                ],
                "summary": "The buyer's return page after an abandoned checkout. HTML.",
                "operationId": "getBillingCancelled",
                "security": [],
                "responses": {
                    "200": {
                        "description": "Success"
                    }
                }
            }
        },
        "/api/users": {
            "get": {
                "tags": [
                    "Users, roles & security"
                ],
                "summary": "Tenant users with their roles, 2FA state, session count and company access.",
                "operationId": "getApiUsers",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/users/invite": {
            "post": {
                "tags": [
                    "Users, roles & security"
                ],
                "summary": "Invite a colleague, choosing their role and which companies they may reach.",
                "operationId": "postApiUsersInvite",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "email": {
                                        "type": "string"
                                    },
                                    "firstName": {
                                        "type": "string"
                                    },
                                    "lastName": {
                                        "type": "string"
                                    },
                                    "role": {
                                        "type": "string"
                                    },
                                    "roleId": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "email": "andy@example.co.za",
                                "firstName": "Example",
                                "lastName": "Example",
                                "role": "Example",
                                "roleId": "<uuid>"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/users/{id}": {
            "put": {
                "tags": [
                    "Users, roles & security"
                ],
                "summary": "Change a user's name, role, active flag or company access.",
                "operationId": "putApiUsersById",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "roleId": {
                                        "type": "string"
                                    },
                                    "isActive": {
                                        "type": "boolean"
                                    }
                                }
                            },
                            "example": {
                                "roleId": "<uuid>",
                                "isActive": true
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "delete": {
                "tags": [
                    "Users, roles & security"
                ],
                "summary": "Deactivate a user and revoke their sessions.",
                "operationId": "deleteApiUsersById",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/users/{id}/revoke-sessions": {
            "post": {
                "tags": [
                    "Users, roles & security"
                ],
                "summary": "Sign a user out everywhere without deactivating them.",
                "operationId": "postApiUsersByIdRevokeSessions",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/users/{id}/transfer-ownership": {
            "post": {
                "tags": [
                    "Users, roles & security"
                ],
                "summary": "Hand the tenant's single owner seat to another user. Gated harder than ordinary user management on purpose: `roles:edit` alone is not enough, because that permission is grantable to a custom role and giving away the god-mode seat is a different order of act from renaming one — only the current owner/admin may call it, and never on themselves. One transaction demotes the outgoing owner and promotes the target together, so the account is never briefly left with none or with two.",
                "operationId": "postApiUsersByIdTransferOwnership",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {}
                            },
                            "example": {}
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/user-sessions": {
            "get": {
                "tags": [
                    "Users, roles & security"
                ],
                "summary": "Live sessions across the tenant — device, address and last seen.",
                "operationId": "getApiUserSessions",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/roles": {
            "get": {
                "tags": [
                    "Users, roles & security"
                ],
                "summary": "System and custom roles. The eight that ship are Owner, Admin, Accountant, Sales, Viewer, Payroll Manager, Tax Practitioner and External Auditor, plus Buyer, Production Planner and Fleet Controller where the plan carries them.",
                "operationId": "getApiRoles",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Users, roles & security"
                ],
                "summary": "Create a custom role.",
                "operationId": "postApiRoles",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string"
                                    },
                                    "description": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "name": "Branch manager",
                                "description": "Sells and banks, cannot see payroll"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/roles/{id}": {
            "put": {
                "tags": [
                    "Users, roles & security"
                ],
                "summary": "Rename or re-describe a role.",
                "operationId": "putApiRolesById",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "name": "Branch manager"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "delete": {
                "tags": [
                    "Users, roles & security"
                ],
                "summary": "Delete a custom role. A system role cannot be deleted.",
                "operationId": "deleteApiRolesById",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/roles/{id}/permissions": {
            "get": {
                "tags": [
                    "Users, roles & security"
                ],
                "summary": "The full permission matrix for one role — every module against view, create, edit, delete, approve and export.",
                "operationId": "getApiRolesByIdPermissions",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "put": {
                "tags": [
                    "Users, roles & security"
                ],
                "summary": "Replace a role's permission matrix.",
                "operationId": "putApiRolesByIdPermissions",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "permissions": {
                                        "type": "array",
                                        "items": {
                                            "type": "object",
                                            "properties": {
                                                "resource": {
                                                    "type": "string"
                                                },
                                                "action": {
                                                    "type": "string"
                                                }
                                            }
                                        }
                                    }
                                }
                            },
                            "example": {
                                "permissions": [
                                    {
                                        "resource": "Example",
                                        "action": "Example"
                                    }
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/roles/my-permissions": {
            "get": {
                "tags": [
                    "Users, roles & security"
                ],
                "summary": "What the calling user may do, resolved. The client's nav guard reads this rather than inferring it from a role name.",
                "operationId": "getApiRolesMyPermissions",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/audit-log": {
            "get": {
                "tags": [
                    "Users, roles & security"
                ],
                "summary": "Audit events with severity filtering. Retained seven years for POPIA.",
                "operationId": "getApiAuditLog",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/security-policy": {
            "get": {
                "tags": [
                    "Users, roles & security"
                ],
                "summary": "Session timeout, password policy, lockout rules, whether admins must hold 2FA, and the retention period.",
                "operationId": "getApiSecurityPolicy",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "put": {
                "tags": [
                    "Users, roles & security"
                ],
                "summary": "Change the security policy. Requiring 2FA for admins is enforced server-side, not advised to the client.",
                "operationId": "putApiSecurityPolicy",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "sessionTimeoutMins": {
                                        "type": "integer"
                                    },
                                    "require2faForAdmins": {
                                        "type": "boolean"
                                    },
                                    "maxFailedLogins": {
                                        "type": "integer"
                                    }
                                }
                            },
                            "example": {
                                "sessionTimeoutMins": 60,
                                "require2faForAdmins": true,
                                "maxFailedLogins": 5
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/api-keys": {
            "get": {
                "tags": [
                    "API keys"
                ],
                "summary": "Keys for this tenant — name, scopes, bound contact, last use. The secret is never returned again.",
                "operationId": "getApiApiKeys",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "API keys"
                ],
                "summary": "Generate a key. The plaintext is shown ONCE in this response. Bind it to a contact if it will post billable events for one.",
                "operationId": "postApiApiKeys",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string"
                                    },
                                    "scopes": {
                                        "type": "array",
                                        "items": {
                                            "type": "string"
                                        }
                                    },
                                    "contactId": {
                                        "type": "null"
                                    }
                                }
                            },
                            "example": {
                                "name": "Warehouse sync",
                                "scopes": [
                                    "inventory:read",
                                    "inventory:write"
                                ],
                                "contactId": null
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "id": {
                                            "type": "string"
                                        },
                                        "name": {
                                            "type": "string"
                                        },
                                        "key": {
                                            "type": "string"
                                        },
                                        "scopes": {
                                            "type": "array",
                                            "items": {
                                                "type": "string"
                                            }
                                        },
                                        "rateLimitPerMinute": {
                                            "type": "integer"
                                        }
                                    }
                                },
                                "example": {
                                    "id": "<uuid>",
                                    "name": "Warehouse sync",
                                    "key": "ledgr_live_… (shown once)",
                                    "scopes": [
                                        "inventory:read",
                                        "inventory:write"
                                    ],
                                    "rateLimitPerMinute": 120
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/api-keys/{id}": {
            "delete": {
                "tags": [
                    "API keys"
                ],
                "summary": "Revoke a key. It stops working on the next request, not when its last token expires.",
                "operationId": "deleteApiApiKeysById",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/api-keys/usage": {
            "get": {
                "tags": [
                    "API keys"
                ],
                "summary": "Per-key call counts and throttling. Metering is per replica, so a tenant's effective limit is N times the published one — see the KDoc on ApiKeyMetering.",
                "operationId": "getApiApiKeysUsage",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/files/{id}": {
            "get": {
                "tags": [
                    "Files & attachments"
                ],
                "summary": "Returns a short-lived signed URL as JSON, deliberately NOT a 302: a redirect would carry your Ledgr token into Google's request logs, because HTTP clients re-send headers across a redirect. A miss answers 404, not 403, so an id cannot be probed for.",
                "operationId": "getApiFilesById",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "url": {
                                            "type": "string"
                                        },
                                        "expiresInSeconds": {
                                            "type": "integer"
                                        },
                                        "contentType": {
                                            "type": "string"
                                        },
                                        "sizeBytes": {
                                            "type": "integer"
                                        }
                                    }
                                },
                                "example": {
                                    "url": "https://storage.googleapis.com/…?X-Goog-Signature=…",
                                    "expiresInSeconds": 900,
                                    "contentType": "image/jpeg",
                                    "sizeBytes": 184320
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/search": {
            "get": {
                "tags": [
                    "Search, insights & deadlines"
                ],
                "summary": "Cross-module global search behind the top bar. Each section is gated separately on plan modules and RBAC, so results never name a record the caller may not open.",
                "operationId": "getApiSearch",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/insights": {
            "get": {
                "tags": [
                    "Search, insights & deadlines"
                ],
                "summary": "Dashboard insights across growth, collections, cash flow, tax and expenses.",
                "operationId": "getApiInsights",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/insights/dismiss": {
            "post": {
                "tags": [
                    "Search, insights & deadlines"
                ],
                "summary": "Dismiss an insight so the carousel stops offering it.",
                "operationId": "postApiInsightsDismiss",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "id": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "id": "<uuid>"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/tax-deadlines": {
            "get": {
                "tags": [
                    "Search, insights & deadlines"
                ],
                "summary": "The SARS calendar for this tenant, gated by its VAT category, employer registration and fiscal year end — so a non-employer is not chased for an EMP201.",
                "operationId": "getApiTaxDeadlines",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/push/status": {
            "get": {
                "tags": [
                    "Push notifications"
                ],
                "summary": "Whether this deployment can send at all, and which devices this user has registered. Both halves matter: a configured server with no device sends nothing, and a device on an unconfigured server receives nothing.",
                "operationId": "getApiPushStatus",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/push/tokens": {
            "post": {
                "tags": [
                    "Push notifications"
                ],
                "summary": "Register this install's FCM token. Idempotent — call it at every sign-in and on every token rotation; the same token for the same user just touches last_seen_at.",
                "operationId": "postApiPushTokens",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "token": {
                                        "type": "string"
                                    },
                                    "platform": {
                                        "type": "string"
                                    },
                                    "deviceId": {
                                        "type": "string"
                                    },
                                    "label": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "token": "<fcm-registration-token>",
                                "platform": "android",
                                "deviceId": "dev-<uuid>",
                                "label": "Andy's phone"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "delete": {
                "tags": [
                    "Push notifications"
                ],
                "summary": "Stop sending to this device. Both fields are optional: deviceId alone is enough when the token is no longer to hand, and neither retires every token this user holds. A sign-out does NOT need this — the server retires the device's tokens from /auth/logout.",
                "operationId": "deleteApiPushTokens",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "token": {
                                        "type": "string"
                                    },
                                    "deviceId": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "token": "<fcm-registration-token>",
                                "deviceId": "dev-<uuid>"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/push/test": {
            "post": {
                "tags": [
                    "Push notifications"
                ],
                "summary": "Send a real push to the caller's own devices and report what happened — the only honest answer to \"is push working\". Goes through the same sender a payment notification does and deliberately ignores the topic switches, so a failure tells you it is broken rather than switched off.",
                "operationId": "postApiPushTest",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/contacts": {
            "get": {
                "tags": [
                    "Contacts"
                ],
                "summary": "List contacts. Filter by ?type=CUSTOMER|SUPPLIER|EMPLOYEE|TAX_PRACTITIONER.",
                "operationId": "getApiContacts",
                "description": "Requires the `contacts:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "contacts:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Contacts"
                ],
                "summary": "Create a contact.",
                "operationId": "postApiContacts",
                "description": "Requires the `contacts:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "contacts:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "contactType": {
                                        "type": "string"
                                    },
                                    "name": {
                                        "type": "string"
                                    },
                                    "email": {
                                        "type": "string"
                                    },
                                    "phone": {
                                        "type": "string"
                                    },
                                    "vatNumber": {
                                        "type": "string"
                                    },
                                    "addressLine1": {
                                        "type": "string"
                                    },
                                    "city": {
                                        "type": "string"
                                    },
                                    "province": {
                                        "type": "string"
                                    },
                                    "creditLimit": {
                                        "type": "number"
                                    },
                                    "paymentTerms": {
                                        "type": "integer"
                                    }
                                }
                            },
                            "example": {
                                "contactType": "customer",
                                "name": "Example",
                                "email": "billing@acme.co.za",
                                "phone": "+27210000000",
                                "vatNumber": "4123456789",
                                "addressLine1": "123 Long Street",
                                "city": "Cape Town",
                                "province": "Western Cape",
                                "creditLimit": 50000.0,
                                "paymentTerms": 30
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "id": {
                                            "type": "string"
                                        },
                                        "name": {
                                            "type": "string"
                                        },
                                        "type": {
                                            "type": "string"
                                        },
                                        "balance": {
                                            "type": "number"
                                        },
                                        "creditLimit": {
                                            "type": "number"
                                        },
                                        "createdAt": {
                                            "type": "string"
                                        }
                                    }
                                },
                                "example": {
                                    "id": "<uuid>",
                                    "name": "Acme Pty Ltd",
                                    "type": "CUSTOMER",
                                    "balance": 0.0,
                                    "creditLimit": 50000.0,
                                    "createdAt": "2026-08-22T09:00:00Z"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/contacts/{id}": {
            "get": {
                "tags": [
                    "Contacts"
                ],
                "summary": "One contact with its full detail grid and credit position.",
                "operationId": "getApiContactsById",
                "description": "Requires the `contacts:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "contacts:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "put": {
                "tags": [
                    "Contacts"
                ],
                "summary": "Update a contact in place.",
                "operationId": "putApiContactsById",
                "description": "Requires the `contacts:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "contacts:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string"
                                    },
                                    "email": {
                                        "type": "string"
                                    },
                                    "creditLimit": {
                                        "type": "number"
                                    }
                                }
                            },
                            "example": {
                                "name": "Acme Pty Ltd",
                                "email": "newbilling@acme.co.za",
                                "creditLimit": 75000.0
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "delete": {
                "tags": [
                    "Contacts"
                ],
                "summary": "Soft-delete a contact.",
                "operationId": "deleteApiContactsById",
                "description": "Requires the `contacts:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "contacts:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/contacts/{id}/tags": {
            "patch": {
                "tags": [
                    "Contacts"
                ],
                "summary": "Set the badges — VIP, At Risk, NPO, B-BBEE level.",
                "operationId": "patchApiContactsByIdTags",
                "description": "Requires the `contacts:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "contacts:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "tags": {
                                        "type": "array",
                                        "items": {
                                            "type": "string"
                                        }
                                    }
                                }
                            },
                            "example": {
                                "tags": [
                                    "VIP",
                                    "BBBEE-2"
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/contacts/{id}/merge": {
            "post": {
                "tags": [
                    "Contacts"
                ],
                "summary": "Merge a duplicate into this contact. Re-points invoices, quotes, activities and communications rather than copying them.",
                "operationId": "postApiContactsByIdMerge",
                "description": "Requires the `contacts:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "contacts:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "survivorId": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "survivorId": "<uuid>"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/contacts/{id}/invoices": {
            "get": {
                "tags": [
                    "Contacts"
                ],
                "summary": "Every document raised against this contact.",
                "operationId": "getApiContactsByIdInvoices",
                "description": "Requires the `contacts:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "contacts:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/contacts/{id}/communications": {
            "get": {
                "tags": [
                    "Contacts"
                ],
                "summary": "The communication timeline — emails, WhatsApps, statements sent, plus CRM activities merged in server-side.",
                "operationId": "getApiContactsByIdCommunications",
                "description": "Requires the `contacts:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "contacts:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Contacts"
                ],
                "summary": "Log a communication by hand.",
                "operationId": "postApiContactsByIdCommunications",
                "description": "Requires the `contacts:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "contacts:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "channel": {
                                        "type": "string"
                                    },
                                    "subject": {
                                        "type": "string"
                                    },
                                    "body": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "channel": "phone",
                                "subject": "Chased the March invoice",
                                "body": "Promised payment Friday."
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/contacts/{id}/statement.pdf": {
            "get": {
                "tags": [
                    "Contacts"
                ],
                "summary": "The customer statement as a PDF.",
                "operationId": "getApiContactsByIdStatement.pdf",
                "description": "Requires the `contacts:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "contacts:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/contacts/{id}/send-statement": {
            "post": {
                "tags": [
                    "Contacts"
                ],
                "summary": "Send the statement by email or WhatsApp.",
                "operationId": "postApiContactsByIdSendStatement",
                "description": "Requires the `contacts:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "contacts:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "channel": {
                                        "type": "string"
                                    },
                                    "asOf": {
                                        "type": "string",
                                        "format": "date"
                                    }
                                }
                            },
                            "example": {
                                "channel": "email",
                                "asOf": "2026-08-31"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/contacts/aged-debtors": {
            "get": {
                "tags": [
                    "Contacts"
                ],
                "summary": "Aged debtors by bucket — current, 30, 60, 90 and over.",
                "operationId": "getApiContactsAgedDebtors",
                "description": "Requires the `contacts:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "contacts:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/contacts/{id}/portal-link": {
            "get": {
                "tags": [
                    "Contacts"
                ],
                "summary": "The customer's self-service portal link. The token travels in the URL path, which is why this is minted rather than guessable.",
                "operationId": "getApiContactsByIdPortalLink",
                "description": "Requires the `contacts:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "contacts:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/contacts/{id}/portal-link/revoke": {
            "post": {
                "tags": [
                    "Contacts"
                ],
                "summary": "Retire whatever portal link this contact is holding, by bumping their token generation.",
                "operationId": "postApiContactsByIdPortalLinkRevoke",
                "description": "Requires the `contacts:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "contacts:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/contacts/{id}/supplier-portal": {
            "get": {
                "tags": [
                    "Contacts"
                ],
                "summary": "Whether this supplier is opted in to the supplier portal, and when they last signed in.",
                "operationId": "getApiContactsByIdSupplierPortal",
                "description": "Requires the `contacts:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "contacts:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Contacts"
                ],
                "summary": "Opt a supplier in and mint their portal credential. Returned once; issuing a new one retires the old.",
                "operationId": "postApiContactsByIdSupplierPortal",
                "description": "Requires the `contacts:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "contacts:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "enabled": {
                                        "type": "boolean"
                                    }
                                }
                            },
                            "example": {
                                "enabled": true
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/contacts/{contactId}/transactions": {
            "get": {
                "tags": [
                    "Contacts"
                ],
                "summary": "Billable events posted against this contact by an external system, before they are invoiced.",
                "operationId": "getApiContactsByContactIdTransactions",
                "description": "Requires the `contacts:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "contacts:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "contactId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/contacts/{contactId}/transactions/invoice": {
            "post": {
                "tags": [
                    "Contacts"
                ],
                "summary": "Roll this contact's pending billable events into a draft invoice.",
                "operationId": "postApiContactsByContactIdTransactionsInvoice",
                "description": "Requires the `contacts:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "contacts:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "contactId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "from": {
                                        "type": "string",
                                        "format": "date"
                                    }
                                }
                            },
                            "example": {
                                "from": "2026-08-01"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/invoices": {
            "get": {
                "tags": [
                    "Invoicing & quotes"
                ],
                "summary": "List documents. The reference paginated endpoint: SQL paging plus five child collections batch-loaded by inList.",
                "operationId": "getApiInvoices",
                "description": "Requires the `invoices:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "invoices:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Invoicing & quotes"
                ],
                "summary": "Create any of the nine document types — invoice, quote, proforma, deposit, credit, debit, delivery, purchase_order, receipt. Send a client-minted id to make the write idempotent for offline replay.",
                "operationId": "postApiInvoices",
                "description": "Requires the `invoices:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "invoices:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "contactId": {
                                        "type": "string"
                                    },
                                    "documentType": {
                                        "type": "string"
                                    },
                                    "issueDate": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "dueDate": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "currency": {
                                        "type": "string"
                                    },
                                    "terms": {
                                        "type": "string"
                                    },
                                    "notes": {
                                        "type": "string"
                                    },
                                    "lines": {
                                        "type": "array",
                                        "items": {
                                            "type": "object",
                                            "properties": {
                                                "description": {
                                                    "type": "string"
                                                },
                                                "kind": {
                                                    "type": "string"
                                                },
                                                "quantity": {
                                                    "type": "number"
                                                },
                                                "unitPrice": {
                                                    "type": "number"
                                                },
                                                "discountRate": {
                                                    "type": "number"
                                                },
                                                "vatRate": {
                                                    "type": "number"
                                                }
                                            }
                                        }
                                    }
                                }
                            },
                            "example": {
                                "contactId": "<uuid>",
                                "documentType": "invoice",
                                "issueDate": "2026-08-10",
                                "dueDate": "2026-09-10",
                                "currency": "ZAR",
                                "terms": "30",
                                "notes": "Thanks for your business.",
                                "lines": [
                                    {
                                        "description": "Mobile app development",
                                        "kind": "service",
                                        "quantity": 1.0,
                                        "unitPrice": 45000.0,
                                        "discountRate": 0.0,
                                        "vatRate": 15.0
                                    }
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "id": {
                                            "type": "string"
                                        },
                                        "documentNumber": {
                                            "type": "string"
                                        },
                                        "documentType": {
                                            "type": "string"
                                        },
                                        "status": {
                                            "type": "string"
                                        },
                                        "subtotal": {
                                            "type": "number"
                                        },
                                        "vatTotal": {
                                            "type": "number"
                                        },
                                        "total": {
                                            "type": "number"
                                        },
                                        "balanceDue": {
                                            "type": "number"
                                        }
                                    }
                                },
                                "example": {
                                    "id": "<uuid>",
                                    "documentNumber": "INV-0042",
                                    "documentType": "invoice",
                                    "status": "draft",
                                    "subtotal": 45000.0,
                                    "vatTotal": 6750.0,
                                    "total": 51750.0,
                                    "balanceDue": 51750.0
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/invoices/{id}": {
            "get": {
                "tags": [
                    "Invoicing & quotes"
                ],
                "summary": "One document with its lines, payments, reminders and attachments.",
                "operationId": "getApiInvoicesById",
                "description": "Requires the `invoices:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "invoices:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "put": {
                "tags": [
                    "Invoicing & quotes"
                ],
                "summary": "Update a document. Note it soft-deletes and re-inserts every line, so anything reading lines for a return must filter deletedAt.",
                "operationId": "putApiInvoicesById",
                "description": "Requires the `invoices:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "invoices:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "status": {
                                        "type": "string"
                                    },
                                    "notes": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "status": "sent",
                                "notes": "Updated note."
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "delete": {
                "tags": [
                    "Invoicing & quotes"
                ],
                "summary": "Void a document. Sets the status; nothing leaves history.",
                "operationId": "deleteApiInvoicesById",
                "description": "Requires the `invoices:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "invoices:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/invoices/summary": {
            "get": {
                "tags": [
                    "Invoicing & quotes"
                ],
                "summary": "The KPI row — outstanding, overdue, paid this month, and drafts.",
                "operationId": "getApiInvoicesSummary",
                "description": "Requires the `invoices:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "invoices:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/invoices/{id}/send": {
            "post": {
                "tags": [
                    "Invoicing & quotes"
                ],
                "summary": "Mark sent and deliver by email or WhatsApp, with the PDF attached if the tenant's settings say so.",
                "operationId": "postApiInvoicesByIdSend",
                "description": "Requires the `invoices:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "invoices:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "channel": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "channel": "email"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/invoices/{id}/payments": {
            "post": {
                "tags": [
                    "Invoicing & quotes"
                ],
                "summary": "Record a payment. Moves the document to partial or paid and advances any CRM deal linked to it.",
                "operationId": "postApiInvoicesByIdPayments",
                "description": "Requires the `invoices:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "invoices:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "amount": {
                                        "type": "number"
                                    },
                                    "method": {
                                        "type": "string"
                                    },
                                    "date": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "reference": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "amount": 51750.0,
                                "method": "EFT",
                                "date": "2026-08-12",
                                "reference": "FNB 4472"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/invoices/{id}/convert": {
            "post": {
                "tags": [
                    "Invoicing & quotes"
                ],
                "summary": "Convert a quote or pro-forma into a tax invoice. Mints the number and posts to the ledger.",
                "operationId": "postApiInvoicesByIdConvert",
                "description": "Requires the `invoices:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "invoices:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "terms": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "terms": "30"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/invoices/{id}/apply-deposit": {
            "post": {
                "tags": [
                    "Invoicing & quotes"
                ],
                "summary": "Apply a deposit invoice against the final invoice, releasing it from Deferred Revenue (2150).",
                "operationId": "postApiInvoicesByIdApplyDeposit",
                "description": "Requires the `invoices:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "invoices:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "invoiceId": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "invoiceId": "<uuid>"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/invoices/{id}/reminders": {
            "post": {
                "tags": [
                    "Invoicing & quotes"
                ],
                "summary": "Send a payment reminder now, outside the configured schedule.",
                "operationId": "postApiInvoicesByIdReminders",
                "description": "Requires the `invoices:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "invoices:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "channel": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "channel": "email"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/invoices/{id}/payment-links": {
            "get": {
                "tags": [
                    "Invoicing & quotes"
                ],
                "summary": "Payment links already minted for this document.",
                "operationId": "getApiInvoicesByIdPaymentLinks",
                "description": "Requires the `invoices:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "invoices:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Invoicing & quotes"
                ],
                "summary": "Mint a Ledgr /pay/{linkId} URL that builds and signs a PayFast checkout at request time. PayFast only — Yoco is refused, because it has no server-side call to obtain a checkout.",
                "operationId": "postApiInvoicesByIdPaymentLinks",
                "description": "Requires the `invoices:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "invoices:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "gateway": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "gateway": "payfast"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "linkId": {
                                            "type": "string"
                                        },
                                        "url": {
                                            "type": "string"
                                        },
                                        "gateway": {
                                            "type": "string"
                                        },
                                        "expiresAt": {
                                            "type": "string"
                                        }
                                    }
                                },
                                "example": {
                                    "linkId": "<uuid>",
                                    "url": "https://ledgr.co.za/pay/<linkId>",
                                    "gateway": "payfast",
                                    "expiresAt": "2026-09-10T00:00:00Z"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/invoices/pdf": {
            "post": {
                "tags": [
                    "Invoicing & quotes"
                ],
                "summary": "Render a document to PDF server-side, from the same HTML the preview and the portal use.",
                "operationId": "postApiInvoicesPdf",
                "description": "Requires the `invoices:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "invoices:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "docType": {
                                        "type": "string"
                                    },
                                    "docTitle": {
                                        "type": "string"
                                    },
                                    "number": {
                                        "type": "string"
                                    },
                                    "date": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "due": {
                                        "type": "string"
                                    },
                                    "status": {
                                        "type": "string"
                                    },
                                    "sender": {
                                        "type": "object",
                                        "properties": {
                                            "name": {
                                                "type": "string"
                                            }
                                        }
                                    },
                                    "client": {
                                        "type": "object",
                                        "properties": {
                                            "name": {
                                                "type": "string"
                                            }
                                        }
                                    },
                                    "lines": {
                                        "type": "array",
                                        "items": {
                                            "type": "object",
                                            "properties": {
                                                "description": {
                                                    "type": "string"
                                                },
                                                "quantity": {
                                                    "type": "number"
                                                },
                                                "unitPrice": {
                                                    "type": "number"
                                                },
                                                "lineTotal": {
                                                    "type": "number"
                                                }
                                            }
                                        }
                                    },
                                    "subtotal": {
                                        "type": "number"
                                    },
                                    "vatTotal": {
                                        "type": "number"
                                    },
                                    "total": {
                                        "type": "number"
                                    }
                                }
                            },
                            "example": {
                                "docType": "invoice",
                                "docTitle": "Example",
                                "number": "Example",
                                "date": "2026-09-30",
                                "due": "Example",
                                "status": "sent",
                                "sender": {
                                    "name": "Example"
                                },
                                "client": {
                                    "name": "Example"
                                },
                                "lines": [
                                    {
                                        "description": "Captured during the August review",
                                        "quantity": 2.0,
                                        "unitPrice": 1500.0,
                                        "lineTotal": 1500.0
                                    }
                                ],
                                "subtotal": 1500.0,
                                "vatTotal": 15.0,
                                "total": 1500.0
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/invoices/number-leases": {
            "get": {
                "tags": [
                    "Invoicing & quotes"
                ],
                "summary": "Document-number leases this device holds. V213 built these so a device can mint real series numbers offline; DocumentNumbering still uses its own local lease and never asks for one.",
                "operationId": "getApiInvoicesNumberLeases",
                "description": "Requires the `invoices:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "invoices:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Invoicing & quotes"
                ],
                "summary": "Reserve a block of numbers in a series.",
                "operationId": "postApiInvoicesNumberLeases",
                "description": "Requires the `invoices:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "invoices:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "nodeId": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "nodeId": "<uuid>"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/invoices/number-leases/release": {
            "post": {
                "tags": [
                    "Invoicing & quotes"
                ],
                "summary": "Give an unused block back so the numbers are not burned.",
                "operationId": "postApiInvoicesNumberLeasesRelease",
                "description": "Requires the `invoices:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "invoices:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "nodeId": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "nodeId": "<uuid>"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/recurring": {
            "get": {
                "tags": [
                    "Invoicing & quotes"
                ],
                "summary": "Recurring invoice schedules.",
                "operationId": "getApiRecurring",
                "description": "Requires the `invoices:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "invoices:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Invoicing & quotes"
                ],
                "summary": "Create a recurring schedule from a template document.",
                "operationId": "postApiRecurring",
                "description": "Requires the `invoices:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "invoices:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "contactId": {
                                        "type": "string"
                                    },
                                    "description": {
                                        "type": "string"
                                    },
                                    "amount": {
                                        "type": "number"
                                    },
                                    "nextDate": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "frequency": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "contactId": "<uuid>",
                                "description": "Captured during the August review",
                                "amount": 1500.0,
                                "nextDate": "2026-09-30",
                                "frequency": "monthly"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/recurring/{id}": {
            "get": {
                "tags": [
                    "Invoicing & quotes"
                ],
                "summary": "One schedule and its issue history.",
                "operationId": "getApiRecurringById",
                "description": "Requires the `invoices:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "invoices:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "put": {
                "tags": [
                    "Invoicing & quotes"
                ],
                "summary": "Change a schedule.",
                "operationId": "putApiRecurringById",
                "description": "Requires the `invoices:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "invoices:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "frequency": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "frequency": "quarterly"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "delete": {
                "tags": [
                    "Invoicing & quotes"
                ],
                "summary": "Delete a schedule. Documents it has already raised are untouched.",
                "operationId": "deleteApiRecurringById",
                "description": "Requires the `invoices:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "invoices:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/recurring/{id}/toggle": {
            "post": {
                "tags": [
                    "Invoicing & quotes"
                ],
                "summary": "Pause or resume a schedule.",
                "operationId": "postApiRecurringByIdToggle",
                "description": "Requires the `invoices:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "invoices:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "active": {
                                        "type": "boolean"
                                    }
                                }
                            },
                            "example": {
                                "active": false
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/invoice-templates": {
            "get": {
                "tags": [
                    "Invoicing & quotes"
                ],
                "summary": "The tenant's templates plus the five presets. A template is a name and an accent colour — the layout is fixed and there is exactly one.",
                "operationId": "getApiInvoiceTemplates",
                "description": "Requires the `invoices:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "invoices:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Invoicing & quotes"
                ],
                "summary": "Add a template. Only name and colour are accepted; the legacy layout_json, secondary_colour, show_logo, show_signature and footer_text columns are read by nothing and no longer accepted.",
                "operationId": "postApiInvoiceTemplates",
                "description": "Requires the `invoices:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "invoices:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string"
                                    },
                                    "primaryColour": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "name": "Site works",
                                "primaryColour": "#00838F"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/invoice-templates/{id}": {
            "get": {
                "tags": [
                    "Invoicing & quotes"
                ],
                "summary": "One template.",
                "operationId": "getApiInvoiceTemplatesById",
                "description": "Requires the `invoices:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "invoices:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "put": {
                "tags": [
                    "Invoicing & quotes"
                ],
                "summary": "Rename or recolour a template.",
                "operationId": "putApiInvoiceTemplatesById",
                "description": "Requires the `invoices:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "invoices:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string"
                                    },
                                    "primaryColour": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "name": "Site works",
                                "primaryColour": "#0D47A1"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "delete": {
                "tags": [
                    "Invoicing & quotes"
                ],
                "summary": "Remove a tenant template. The five presets cannot be removed.",
                "operationId": "deleteApiInvoiceTemplatesById",
                "description": "Requires the `invoices:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "invoices:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/invoice-settings": {
            "get": {
                "tags": [
                    "Invoicing & quotes"
                ],
                "summary": "The seven-tab settings centre — numbering, labels, field visibility, email templates, branding, defaults and auto-reconciliation.",
                "operationId": "getApiInvoiceSettings",
                "description": "Requires the `invoices:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "invoices:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "put": {
                "tags": [
                    "Invoicing & quotes"
                ],
                "summary": "Replace the invoice settings.",
                "operationId": "putApiInvoiceSettings",
                "description": "Requires the `invoices:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "invoices:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "activeTemplateName": {
                                        "type": "string"
                                    },
                                    "show": {
                                        "type": "object",
                                        "properties": {
                                            "dueDate": {
                                                "type": "boolean"
                                            },
                                            "contactName": {
                                                "type": "boolean"
                                            },
                                            "businessAddress": {
                                                "type": "boolean"
                                            },
                                            "vatNumber": {
                                                "type": "boolean"
                                            },
                                            "discountColumn": {
                                                "type": "boolean"
                                            },
                                            "paymentLinks": {
                                                "type": "boolean"
                                            },
                                            "associationLogo": {
                                                "type": "boolean"
                                            }
                                        }
                                    },
                                    "defaults": {
                                        "type": "object",
                                        "properties": {
                                            "terms": {
                                                "type": "string"
                                            },
                                            "bankDetails": {
                                                "type": "string"
                                            },
                                            "footer": {
                                                "type": "string"
                                            },
                                            "notes": {
                                                "type": "string"
                                            }
                                        }
                                    },
                                    "autoRecon": {
                                        "type": "object",
                                        "properties": {
                                            "enabled": {
                                                "type": "boolean"
                                            },
                                            "yoco": {
                                                "type": "boolean"
                                            },
                                            "payfast": {
                                                "type": "boolean"
                                            },
                                            "peach": {
                                                "type": "boolean"
                                            },
                                            "matchToleranceZar": {
                                                "type": "number"
                                            }
                                        }
                                    }
                                }
                            },
                            "example": {
                                "activeTemplateName": "Site works",
                                "show": {
                                    "dueDate": true,
                                    "contactName": true,
                                    "businessAddress": true,
                                    "vatNumber": true,
                                    "discountColumn": true,
                                    "paymentLinks": true,
                                    "associationLogo": true
                                },
                                "defaults": {
                                    "terms": "Payment within 30 days.",
                                    "bankDetails": "",
                                    "footer": "",
                                    "notes": ""
                                },
                                "autoRecon": {
                                    "enabled": true,
                                    "yoco": false,
                                    "payfast": true,
                                    "peach": false,
                                    "matchToleranceZar": 2.0
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/document-sequences": {
            "get": {
                "tags": [
                    "Invoicing & quotes"
                ],
                "summary": "The prefix and next number for each of the nine document types.",
                "operationId": "getApiDocumentSequences",
                "description": "Requires the `invoices:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "invoices:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "put": {
                "tags": [
                    "Invoicing & quotes"
                ],
                "summary": "Change a prefix or a starting number. A number already issued is never reissued.",
                "operationId": "putApiDocumentSequences",
                "description": "Requires the `invoices:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "invoices:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "docType": {
                                        "type": "string"
                                    },
                                    "prefix": {
                                        "type": "string"
                                    },
                                    "nextNumber": {
                                        "type": "integer"
                                    }
                                }
                            },
                            "example": {
                                "docType": "invoice",
                                "prefix": "REF-0042",
                                "nextNumber": 1001
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/reminder-configs": {
            "get": {
                "tags": [
                    "Invoicing & quotes"
                ],
                "summary": "The automated reminder schedule — how many days before and after due, and on which channel.",
                "operationId": "getApiReminderConfigs",
                "description": "Requires the `invoices:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "invoices:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "put": {
                "tags": [
                    "Invoicing & quotes"
                ],
                "summary": "Change the reminder schedule.",
                "operationId": "putApiReminderConfigs",
                "description": "Requires the `invoices:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "invoices:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "enabled": {
                                        "type": "boolean"
                                    },
                                    "steps": {
                                        "type": "array",
                                        "items": {
                                            "type": "object",
                                            "properties": {
                                                "days": {
                                                    "type": "integer"
                                                },
                                                "email": {
                                                    "type": "boolean"
                                                },
                                                "whatsapp": {
                                                    "type": "boolean"
                                                }
                                            }
                                        }
                                    },
                                    "message": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "enabled": true,
                                "steps": [
                                    {
                                        "days": 3,
                                        "email": true,
                                        "whatsapp": true
                                    },
                                    {
                                        "days": 7,
                                        "email": true,
                                        "whatsapp": false
                                    },
                                    {
                                        "days": 14,
                                        "email": true,
                                        "whatsapp": false
                                    }
                                ],
                                "message": ""
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/invoices/{id}/attachments": {
            "get": {
                "tags": [
                    "Invoicing & quotes"
                ],
                "summary": "Files filed against a sales document (V290) — the customer's signed purchase order, a site drawing, a spec sheet. The bytes are not in the response: fetch each `attachmentId` through GET /api/files/{id}, which returns a signed URL as JSON.",
                "operationId": "getApiInvoicesByIdAttachments",
                "description": "Requires the `invoices:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "invoices:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Invoicing & quotes"
                ],
                "summary": "Attach a file. RAW BYTES as the body, not multipart and not base64 — base64 inflates a 4 MB scan by a third on exactly the connection this product is designed for. Ten per document, 15 MB each, closed type list (see InvoiceAttachmentPolicy). Never sent to the customer.",
                "operationId": "postApiInvoicesByIdAttachments",
                "description": "Requires the `invoices:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "invoices:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/invoices/{id}/attachments/{attachmentId}": {
            "delete": {
                "tags": [
                    "Invoicing & quotes"
                ],
                "summary": "Unlink a file from the document. The stored object is NOT destroyed — an invoicing screen is not where evidence behind a disputed invoice should be shreddable.",
                "operationId": "deleteApiInvoicesByIdAttachmentsByAttachmentId",
                "description": "Requires the `invoices:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "invoices:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    },
                    {
                        "name": "attachmentId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/payment-gateways": {
            "get": {
                "tags": [
                    "Payments & gateways"
                ],
                "summary": "Which gateways can actually take a payment on this deployment. The single source of truth both sides read — the client greys the rest out rather than offering a dead link.",
                "operationId": "getApiPaymentGateways",
                "description": "Requires the `invoices:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "invoices:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/payment-gateways/payfast/credentials": {
            "get": {
                "tags": [
                    "Payments & gateways"
                ],
                "summary": "The tenant's PayFast merchant details. The key is write-only and comes back only as a masked hint. Read is gated on invoices:view.",
                "operationId": "getApiPaymentGatewaysPayfastCredentials",
                "description": "Requires the `invoices:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "invoices:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "put": {
                "tags": [
                    "Payments & gateways"
                ],
                "summary": "Store PayFast merchant credentials, encrypted with SecretBox. Gated on banking:edit, not invoices:edit — this decides which bank account receives the money.",
                "operationId": "putApiPaymentGatewaysPayfastCredentials",
                "description": "Requires the `invoices:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "invoices:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "merchantId": {
                                        "type": "string"
                                    },
                                    "merchantKey": {
                                        "type": "string"
                                    },
                                    "passphrase": {
                                        "type": "string"
                                    },
                                    "companyScoped": {
                                        "type": "boolean"
                                    }
                                }
                            },
                            "example": {
                                "merchantId": "10000100",
                                "merchantKey": "46f0cd694581a",
                                "passphrase": "jt7NOE43FZPn",
                                "companyScoped": true
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "delete": {
                "tags": [
                    "Payments & gateways"
                ],
                "summary": "Remove stored credentials. Payment links already minted stop resolving a merchant.",
                "operationId": "deleteApiPaymentGatewaysPayfastCredentials",
                "description": "Requires the `invoices:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "invoices:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/payment-gateways/yoco/credentials": {
            "get": {
                "tags": [
                    "Payments & gateways"
                ],
                "summary": "The tenant's Yoco account. The secret key is write-only and comes back only as a masked hint. Also returns the exact webhook URL to register in the Yoco portal, and what is still missing before a payment could be reported. Read is gated on invoices:view.",
                "operationId": "getApiPaymentGatewaysYocoCredentials",
                "description": "Requires the `invoices:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "invoices:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "put": {
                "tags": [
                    "Payments & gateways"
                ],
                "summary": "Store the tenant's Yoco secret key and webhook signing secret, encrypted with SecretBox. Gated on banking:edit, not invoices:edit — this decides which bank account receives the money. Omitting webhookSecret leaves any stored one alone, because the two halves arrive separately: Yoco only reveals the signing secret once the webhook is registered, and registering it needs the URL this endpoint returns.",
                "operationId": "putApiPaymentGatewaysYocoCredentials",
                "description": "Requires the `invoices:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "invoices:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "secretKey": {
                                        "type": "string"
                                    },
                                    "webhookSecret": {
                                        "type": "string"
                                    },
                                    "companyScoped": {
                                        "type": "boolean"
                                    }
                                }
                            },
                            "example": {
                                "secretKey": "sk_test_abcdefghij1234567890",
                                "webhookSecret": "whsec_abcdefghij1234567890",
                                "companyScoped": true
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "delete": {
                "tags": [
                    "Payments & gateways"
                ],
                "summary": "Remove the stored Yoco account. Payment links already minted stop resolving an account.",
                "operationId": "deleteApiPaymentGatewaysYocoCredentials",
                "description": "Requires the `invoices:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "invoices:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/webhook-events": {
            "get": {
                "tags": [
                    "Payments & gateways"
                ],
                "summary": "The payments inbox — incoming gateway events with their match state, matched, review or unmatched.",
                "operationId": "getApiWebhookEvents",
                "description": "Requires the `invoices:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "invoices:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/webhook-events/{id}/match": {
            "post": {
                "tags": [
                    "Payments & gateways"
                ],
                "summary": "Match an unmatched payment to an invoice by hand.",
                "operationId": "postApiWebhookEventsByIdMatch",
                "description": "Requires the `invoices:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "invoices:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "invoiceNumber": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "invoiceNumber": "Example"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/webhook-events/simulate": {
            "post": {
                "tags": [
                    "Payments & gateways"
                ],
                "summary": "Post a synthetic gateway event, for demos and for exercising the matching rules without a real payment.",
                "operationId": "postApiWebhookEventsSimulate",
                "description": "Requires the `invoices:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "invoices:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "gateway": {
                                        "type": "string"
                                    },
                                    "amount": {
                                        "type": "number"
                                    }
                                }
                            },
                            "example": {
                                "gateway": "payfast",
                                "amount": 51750.0
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/pay/{linkId}": {
            "get": {
                "tags": [
                    "Payments & gateways"
                ],
                "summary": "The customer-facing pay page. Public by necessity — the person paying is not a Ledgr user — and guarded by the link id being an unguessable capability.",
                "operationId": "getPayByLinkId",
                "security": [],
                "parameters": [
                    {
                        "name": "linkId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    }
                }
            }
        },
        "/pay/{linkId}/done": {
            "get": {
                "tags": [
                    "Payments & gateways"
                ],
                "summary": "Where PayFast returns the payer after a successful checkout.",
                "operationId": "getPayByLinkIdDone",
                "security": [],
                "parameters": [
                    {
                        "name": "linkId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    }
                }
            }
        },
        "/pay/{linkId}/cancelled": {
            "get": {
                "tags": [
                    "Payments & gateways"
                ],
                "summary": "Where PayFast returns the payer after an abandoned checkout.",
                "operationId": "getPayByLinkIdCancelled",
                "security": [],
                "parameters": [
                    {
                        "name": "linkId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    }
                }
            }
        },
        "/portal/invoice/{token}": {
            "get": {
                "tags": [
                    "Customer portal"
                ],
                "summary": "The customer's copy of a document, with pay, accept and decline where they apply.",
                "operationId": "getPortalInvoiceByToken",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "token",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/portal/invoice/{token}/pdf": {
            "get": {
                "tags": [
                    "Customer portal"
                ],
                "summary": "The same document as a PDF.",
                "operationId": "getPortalInvoiceByTokenPdf",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "token",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/portal/invoice/{token}/{decision}": {
            "post": {
                "tags": [
                    "Customer portal"
                ],
                "summary": "Accept or decline a quote online. A form post with a 303 redirect; JSON is accepted too. Records decided_at, the typed name as the signature and a decline reason — deliberately NO IP and no user agent, both of which are personal information needing a purpose and a retention position of their own.",
                "operationId": "postPortalInvoiceByTokenByDecision",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "token",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "decision",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string"
                                    },
                                    "reason": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "name": "T. Nkosi",
                                "reason": "Went with another supplier"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/portal/contact/{token}": {
            "get": {
                "tags": [
                    "Customer portal"
                ],
                "summary": "The customer's self-service view — their documents, their statement and their balance.",
                "operationId": "getPortalContactByToken",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "token",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/crm/leads": {
            "get": {
                "tags": [
                    "Sales CRM"
                ],
                "summary": "Leads, paginated. The client reports \"50 of 312\" because a list that silently stops at fifty while looking complete is the failure paging was meant to fix.",
                "operationId": "getApiCrmLeads",
                "description": "Requires the `crm:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "crm:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Sales CRM"
                ],
                "summary": "Capture a lead.",
                "operationId": "postApiCrmLeads",
                "description": "Requires the `crm:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "crm:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string"
                                    },
                                    "companyName": {
                                        "type": "string"
                                    },
                                    "email": {
                                        "type": "string"
                                    },
                                    "phone": {
                                        "type": "string"
                                    },
                                    "source": {
                                        "type": "string"
                                    },
                                    "estimatedValue": {
                                        "type": "number"
                                    },
                                    "ownerUserId": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "name": "Sipho Dlamini",
                                "companyName": "Dlamini Logistics",
                                "email": "sipho@dlamini.co.za",
                                "phone": "+27821234567",
                                "source": "referral",
                                "estimatedValue": 85000.0,
                                "ownerUserId": "<uuid>"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/crm/leads/{id}": {
            "get": {
                "tags": [
                    "Sales CRM"
                ],
                "summary": "One lead with its activities.",
                "operationId": "getApiCrmLeadsById",
                "description": "Requires the `crm:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "crm:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "put": {
                "tags": [
                    "Sales CRM"
                ],
                "summary": "Update a lead or move its status through new, contacted, qualified, unqualified.",
                "operationId": "putApiCrmLeadsById",
                "description": "Requires the `crm:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "crm:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "status": {
                                        "type": "string"
                                    },
                                    "estimatedValue": {
                                        "type": "number"
                                    }
                                }
                            },
                            "example": {
                                "status": "qualified",
                                "estimatedValue": 92000.0
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "delete": {
                "tags": [
                    "Sales CRM"
                ],
                "summary": "Delete a lead.",
                "operationId": "deleteApiCrmLeadsById",
                "description": "Requires the `crm:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "crm:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/crm/leads/{id}/convert": {
            "post": {
                "tags": [
                    "Sales CRM"
                ],
                "summary": "Convert to a real contact, carrying the lead's activities across. Never duplicates a customer on the same email — the match runs through the same contactMatchKey the import uses.",
                "operationId": "postApiCrmLeadsByIdConvert",
                "description": "Requires the `crm:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "crm:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "createOpportunity": {
                                        "type": "boolean"
                                    }
                                }
                            },
                            "example": {
                                "createOpportunity": true
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/crm/leads/import": {
            "post": {
                "tags": [
                    "Sales CRM"
                ],
                "summary": "Import leads from CSV. Set preview to run the same code and return the counts without writing. Deliberately forgiving: the only rejection is a row with no name, and money goes through parseFlexibleAmount because a pasted \"R1 500,50\" is what a South African spreadsheet contains.",
                "operationId": "postApiCrmLeadsImport",
                "description": "Requires the `crm:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "crm:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/crm/pipeline": {
            "get": {
                "tags": [
                    "Sales CRM"
                ],
                "summary": "The kanban board — every open deal by stage, with count, total and weighted total per column.",
                "operationId": "getApiCrmPipeline",
                "description": "Requires the `crm:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "crm:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/crm/opportunities": {
            "get": {
                "tags": [
                    "Sales CRM"
                ],
                "summary": "Deals as a table, the same data the board renders.",
                "operationId": "getApiCrmOpportunities",
                "description": "Requires the `crm:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "crm:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Sales CRM"
                ],
                "summary": "Create a deal. A deal with no lines keeps its typed value, which is the normal early state rather than a fallback.",
                "operationId": "postApiCrmOpportunities",
                "description": "Requires the `crm:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "crm:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string"
                                    },
                                    "contactId": {
                                        "type": "string"
                                    },
                                    "stageId": {
                                        "type": "string"
                                    },
                                    "value": {
                                        "type": "number"
                                    },
                                    "expectedCloseDate": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "ownerUserId": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "name": "Warehouse expansion",
                                "contactId": "<uuid>",
                                "stageId": "<uuid>",
                                "value": 26700.0,
                                "expectedCloseDate": "2026-10-31",
                                "ownerUserId": "<uuid>"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/crm/opportunities/{id}": {
            "get": {
                "tags": [
                    "Sales CRM"
                ],
                "summary": "One deal, with its line count so a single-line deal is told apart from a priced one at the call site.",
                "operationId": "getApiCrmOpportunitiesById",
                "description": "Requires the `crm:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "crm:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "put": {
                "tags": [
                    "Sales CRM"
                ],
                "summary": "Update a deal. Once it has lines, value is DERIVED — recomputeOpportunityValue is its only writer.",
                "operationId": "putApiCrmOpportunitiesById",
                "description": "Requires the `crm:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "crm:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string"
                                    },
                                    "expectedCloseDate": {
                                        "type": "string",
                                        "format": "date"
                                    }
                                }
                            },
                            "example": {
                                "name": "Warehouse expansion phase 2",
                                "expectedCloseDate": "2026-11-30"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "delete": {
                "tags": [
                    "Sales CRM"
                ],
                "summary": "Delete a deal and its lines.",
                "operationId": "deleteApiCrmOpportunitiesById",
                "description": "Requires the `crm:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "crm:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/crm/opportunities/{id}/stage": {
            "post": {
                "tags": [
                    "Sales CRM"
                ],
                "summary": "Move a deal. Every move writes a history row, which is where cycle time comes from.",
                "operationId": "postApiCrmOpportunitiesByIdStage",
                "description": "Requires the `crm:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "crm:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "stageId": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "stageId": "<uuid>"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/crm/opportunities/{id}/history": {
            "get": {
                "tags": [
                    "Sales CRM"
                ],
                "summary": "Every stage this deal has been through, and when.",
                "operationId": "getApiCrmOpportunitiesByIdHistory",
                "description": "Requires the `crm:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "crm:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/crm/opportunities/{id}/lines": {
            "get": {
                "tags": [
                    "Sales CRM"
                ],
                "summary": "The deal's lines. They mirror invoice_lines field for field, so a line becomes an InvoiceLineRequest with no conversion step.",
                "operationId": "getApiCrmOpportunitiesByIdLines",
                "description": "Requires the `crm:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "crm:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "put": {
                "tags": [
                    "Sales CRM"
                ],
                "summary": "Replace the deal's lines and recompute its value in the same transaction. unit_cost is the one column invoice_lines does not have — snapshotted at pricing, nullable rather than zero, and it is what lets the board show margin on a deal that has not been invoiced.",
                "operationId": "putApiCrmOpportunitiesByIdLines",
                "description": "Requires the `crm:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "crm:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "array",
                                "items": {
                                    "type": "object",
                                    "properties": {
                                        "description": {
                                            "type": "string"
                                        },
                                        "quantity": {
                                            "type": "number"
                                        },
                                        "unitPrice": {
                                            "type": "number"
                                        },
                                        "unitCost": {
                                            "type": "number"
                                        },
                                        "vatRate": {
                                            "type": "number"
                                        }
                                    }
                                }
                            },
                            "example": [
                                {
                                    "description": "Steel frame",
                                    "quantity": 40.0,
                                    "unitPrice": 1850.0,
                                    "unitCost": 1240.0,
                                    "vatRate": 15.0
                                }
                            ]
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/crm/opportunities/{id}/quote": {
            "post": {
                "tags": [
                    "Sales CRM"
                ],
                "summary": "Raise a real quote through the invoicing engine from the deal's own lines, and store the link both ways.",
                "operationId": "postApiCrmOpportunitiesByIdQuote",
                "description": "Requires the `crm:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "crm:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "issueDate": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "validUntil": {
                                        "type": "string",
                                        "format": "date"
                                    }
                                }
                            },
                            "example": {
                                "issueDate": "2026-08-22",
                                "validUntil": "2026-09-22"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/crm/stages": {
            "get": {
                "tags": [
                    "Sales CRM"
                ],
                "summary": "The tenant's pipeline stages and their default probabilities.",
                "operationId": "getApiCrmStages",
                "description": "Requires the `crm:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "crm:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Sales CRM"
                ],
                "summary": "Add a stage.",
                "operationId": "postApiCrmStages",
                "description": "Requires the `crm:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "crm:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string"
                                    },
                                    "defaultProbability": {
                                        "type": "integer"
                                    },
                                    "sortOrder": {
                                        "type": "integer"
                                    }
                                }
                            },
                            "example": {
                                "name": "Site survey",
                                "defaultProbability": 35,
                                "sortOrder": 2
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/crm/stages/{id}": {
            "put": {
                "tags": [
                    "Sales CRM"
                ],
                "summary": "Rename, reorder or reprobability a stage.",
                "operationId": "putApiCrmStagesById",
                "description": "Requires the `crm:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "crm:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "name": "Example"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "delete": {
                "tags": [
                    "Sales CRM"
                ],
                "summary": "Remove a stage. Refused while deals sit in it.",
                "operationId": "deleteApiCrmStagesById",
                "description": "Requires the `crm:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "crm:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/crm/activities": {
            "get": {
                "tags": [
                    "Sales CRM"
                ],
                "summary": "Calls, meetings, emails, notes and tasks, paginated. Linked polymorphically to a lead, a deal or a contact.",
                "operationId": "getApiCrmActivities",
                "description": "Requires the `crm:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "crm:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Sales CRM"
                ],
                "summary": "Log an activity or set a task. Logging one updates the contact's last_contacted_at, which is what makes \"nobody has called these fifteen customers in four months\" answerable.",
                "operationId": "postApiCrmActivities",
                "description": "Requires the `crm:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "crm:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "activityType": {
                                        "type": "string"
                                    },
                                    "subject": {
                                        "type": "string"
                                    },
                                    "dueDate": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "ownerUserId": {
                                        "type": "string"
                                    },
                                    "opportunityId": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "activityType": "call",
                                "subject": "Follow up on the frame quote",
                                "dueDate": "2026-08-28",
                                "ownerUserId": "<uuid>",
                                "opportunityId": "<uuid>"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/crm/activities/{id}": {
            "put": {
                "tags": [
                    "Sales CRM"
                ],
                "summary": "Change an activity.",
                "operationId": "putApiCrmActivitiesById",
                "description": "Requires the `crm:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "crm:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "dueDate": {
                                        "type": "string",
                                        "format": "date"
                                    }
                                }
                            },
                            "example": {
                                "dueDate": "2026-08-29"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "delete": {
                "tags": [
                    "Sales CRM"
                ],
                "summary": "Delete an activity.",
                "operationId": "deleteApiCrmActivitiesById",
                "description": "Requires the `crm:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "crm:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/crm/activities/{id}/complete": {
            "post": {
                "tags": [
                    "Sales CRM"
                ],
                "summary": "Mark a task done.",
                "operationId": "postApiCrmActivitiesByIdComplete",
                "description": "Requires the `crm:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "crm:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/crm/follow-ups": {
            "get": {
                "tags": [
                    "Sales CRM"
                ],
                "summary": "My open tasks, oldest due first — the read the crm-tasks digest chases. No \"upcoming this week\": a digest listing work that is not yet due trains people to ignore it.",
                "operationId": "getApiCrmFollowUps",
                "description": "Requires the `crm:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "crm:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/crm/owners": {
            "get": {
                "tags": [
                    "Sales CRM"
                ],
                "summary": "Who can own a lead, a deal or a task.",
                "operationId": "getApiCrmOwners",
                "description": "Requires the `crm:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "crm:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/crm/performance": {
            "get": {
                "tags": [
                    "Sales CRM"
                ],
                "summary": "Pipeline by stage, weighted forecast, win rate, average sales cycle, and per-owner and per-source breakdowns.",
                "operationId": "getApiCrmPerformance",
                "description": "Requires the `crm:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "crm:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/crm/targets": {
            "get": {
                "tags": [
                    "Sales CRM"
                ],
                "summary": "Sales targets by period. owner_user_id null means the team's number. Attainment is NOT capped at 100, and no target renders as \"no target set\" rather than 0%.",
                "operationId": "getApiCrmTargets",
                "description": "Requires the `crm:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "crm:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Sales CRM"
                ],
                "summary": "Set a target. Measured against won deal value net of VAT, attributed by closed_at — the period it was won in, not the one somebody hoped.",
                "operationId": "postApiCrmTargets",
                "description": "Requires the `crm:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "crm:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "periodStart": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "periodEnd": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "targetValue": {
                                        "type": "number"
                                    },
                                    "ownerUserId": {
                                        "type": "null"
                                    }
                                }
                            },
                            "example": {
                                "periodStart": "2026-09-01",
                                "periodEnd": "2026-09-30",
                                "targetValue": 250000.0,
                                "ownerUserId": null
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/crm/targets/{id}": {
            "put": {
                "tags": [
                    "Sales CRM"
                ],
                "summary": "Change a target.",
                "operationId": "putApiCrmTargetsById",
                "description": "Requires the `crm:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "crm:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "periodStart": {
                                        "type": "string"
                                    },
                                    "periodEnd": {
                                        "type": "string"
                                    },
                                    "targetValue": {
                                        "type": "number"
                                    }
                                }
                            },
                            "example": {
                                "periodStart": "Example",
                                "periodEnd": "Example",
                                "targetValue": 300000.0
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "delete": {
                "tags": [
                    "Sales CRM"
                ],
                "summary": "Remove a target.",
                "operationId": "deleteApiCrmTargetsById",
                "description": "Requires the `crm:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "crm:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/crm/campaigns": {
            "get": {
                "tags": [
                    "Sales CRM"
                ],
                "summary": "Campaigns with channel, window, budget against spend, and attributed leads, deals and won revenue.",
                "operationId": "getApiCrmCampaigns",
                "description": "Requires the `crm:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "crm:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Sales CRM"
                ],
                "summary": "Create a campaign. Attribution is by the lead's source, so it is only as honest as the capture.",
                "operationId": "postApiCrmCampaigns",
                "description": "Requires the `crm:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "crm:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string"
                                    },
                                    "channel": {
                                        "type": "string"
                                    },
                                    "startDate": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "endDate": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "budget": {
                                        "type": "number"
                                    }
                                }
                            },
                            "example": {
                                "name": "Spring trade show",
                                "channel": "event",
                                "startDate": "2026-09-01",
                                "endDate": "2026-09-30",
                                "budget": 40000.0
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/crm/campaigns/{id}": {
            "put": {
                "tags": [
                    "Sales CRM"
                ],
                "summary": "Update a campaign or record spend against it.",
                "operationId": "putApiCrmCampaignsById",
                "description": "Requires the `crm:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "crm:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "spend": {
                                        "type": "number"
                                    }
                                }
                            },
                            "example": {
                                "spend": 38200.0
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "delete": {
                "tags": [
                    "Sales CRM"
                ],
                "summary": "Delete a campaign.",
                "operationId": "deleteApiCrmCampaignsById",
                "description": "Requires the `crm:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "crm:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/crm/attachments": {
            "get": {
                "tags": [
                    "Sales CRM"
                ],
                "summary": "Files linked to a deal, a lead or a contact. A link table rather than a column, because a deal has several and a single column means the second upload silently replaces the first.",
                "operationId": "getApiCrmAttachments",
                "description": "Requires the `crm:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "crm:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Sales CRM"
                ],
                "summary": "Link an attachment to a deal, a lead or a contact.",
                "operationId": "postApiCrmAttachments",
                "description": "Requires the `crm:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "crm:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "opportunityId": {
                                        "type": "string"
                                    },
                                    "attachmentId": {
                                        "type": "string"
                                    },
                                    "label": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "opportunityId": "<uuid>",
                                "attachmentId": "<uuid>",
                                "label": "Signed acceptance"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/crm/attachments/upload": {
            "post": {
                "tags": [
                    "Sales CRM"
                ],
                "summary": "Store a file and return its id. Raw bytes with the content type in the header, never base64 — a picker hands the client a ByteArray on every target and base64 inflates a 4MB drawing by a third. The server decides the stored content type from a closed list, never the one the client claims. The id then goes to POST /api/crm/attachments, which is what decides where it may be attached.",
                "operationId": "postApiCrmAttachmentsUpload",
                "description": "Requires the `crm:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "crm:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/crm/attachments/{id}": {
            "delete": {
                "tags": [
                    "Sales CRM"
                ],
                "summary": "Unlink. Removes the LINK, not the file — the same attachment may be referenced elsewhere, and a CRM screen is not where somebody should be able to destroy a receipt.",
                "operationId": "deleteApiCrmAttachmentsById",
                "description": "Requires the `crm:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "crm:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/crm/contacts/{id}/summary": {
            "get": {
                "tags": [
                    "Sales CRM"
                ],
                "summary": "The CRM panel inside the contact detail view — open deals, recent activities and lifecycle stage. One contact record, two views onto it.",
                "operationId": "getApiCrmContactsByIdSummary",
                "description": "Requires the `crm:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "crm:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/crm/contacts/{id}": {
            "put": {
                "tags": [
                    "Sales CRM"
                ],
                "summary": "Set the CRM enrichment — lifecycle stage, industry, website and next follow-up.",
                "operationId": "putApiCrmContactsById",
                "description": "Requires the `crm:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "crm:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "lifecycleStage": {
                                        "type": "string"
                                    },
                                    "industry": {
                                        "type": "string"
                                    },
                                    "website": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "lifecycleStage": "customer",
                                "industry": "Logistics",
                                "website": "https://dlamini.co.za"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/expenses": {
            "get": {
                "tags": [
                    "Expenses & claims"
                ],
                "summary": "Expense entries. Served through OfflineCache on the client, which caches the whole list per status and merges pending local writes on top.",
                "operationId": "getApiExpenses",
                "description": "Requires the `expenses:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "expenses:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Expenses & claims"
                ],
                "summary": "Capture an expense. Accepts a client-minted id, which is what makes offline replay idempotent — the id becomes the row's primary key.",
                "operationId": "postApiExpenses",
                "description": "Requires the `expenses:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "expenses:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "date": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "contactId": {
                                        "type": "string"
                                    },
                                    "amount": {
                                        "type": "number"
                                    },
                                    "category": {
                                        "type": "string"
                                    },
                                    "description": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "date": "2026-08-10",
                                "contactId": "<uuid>",
                                "amount": 1234.56,
                                "category": "Travel",
                                "description": "Client meeting in JHB"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/expenses/{id}": {
            "get": {
                "tags": [
                    "Expenses & claims"
                ],
                "summary": "One expense with its receipt and approval trail.",
                "operationId": "getApiExpensesById",
                "description": "Requires the `expenses:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "expenses:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "put": {
                "tags": [
                    "Expenses & claims"
                ],
                "summary": "Update an expense.",
                "operationId": "putApiExpensesById",
                "description": "Requires the `expenses:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "expenses:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "category": {
                                        "type": "string"
                                    },
                                    "description": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "category": "Entertainment",
                                "description": "Client lunch"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "delete": {
                "tags": [
                    "Expenses & claims"
                ],
                "summary": "Delete an expense.",
                "operationId": "deleteApiExpensesById",
                "description": "Requires the `expenses:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "expenses:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/expenses/{id}/submit": {
            "post": {
                "tags": [
                    "Expenses & claims"
                ],
                "summary": "Submit for approval.",
                "operationId": "postApiExpensesByIdSubmit",
                "description": "Requires the `expenses:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "expenses:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/expenses/{id}/approve": {
            "post": {
                "tags": [
                    "Expenses & claims"
                ],
                "summary": "Approve. This is a write transaction that runs a full GL posting, so it goes through dbQuery rather than on the request thread.",
                "operationId": "postApiExpensesByIdApprove",
                "description": "Requires the `expenses:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "expenses:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/expenses/{id}/reject": {
            "post": {
                "tags": [
                    "Expenses & claims"
                ],
                "summary": "Reject, with a reason.",
                "operationId": "postApiExpensesByIdReject",
                "description": "Requires the `expenses:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "expenses:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "reason": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "reason": "No VAT invoice attached"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/expenses/{id}/pay": {
            "post": {
                "tags": [
                    "Expenses & claims"
                ],
                "summary": "Record the reimbursement.",
                "operationId": "postApiExpensesByIdPay",
                "description": "Requires the `expenses:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "expenses:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "bankAccountId": {
                                        "type": "string"
                                    },
                                    "date": {
                                        "type": "string",
                                        "format": "date"
                                    }
                                }
                            },
                            "example": {
                                "bankAccountId": "<uuid>",
                                "date": "2026-08-25"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/expenses/receipts": {
            "post": {
                "tags": [
                    "Expenses & claims"
                ],
                "summary": "Upload a receipt image. The bytes go to object storage; the row holds the reference.",
                "operationId": "postApiExpensesReceipts",
                "description": "Requires the `expenses:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "expenses:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/expenses/receipts/{id}": {
            "get": {
                "tags": [
                    "Expenses & claims"
                ],
                "summary": "The legacy base64 receipt read path, kept until the attachment backfill finishes. New code should use GET /api/files/{id}.",
                "operationId": "getApiExpensesReceiptsById",
                "description": "Requires the `expenses:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "expenses:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/expenses/ocr": {
            "post": {
                "tags": [
                    "Expenses & claims"
                ],
                "summary": "Run Tesseract over a receipt and return the legacy field shape.",
                "operationId": "postApiExpensesOcr",
                "description": "Requires the `expenses:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "expenses:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/expenses/category-budgets": {
            "get": {
                "tags": [
                    "Expenses & claims"
                ],
                "summary": "Budget against actual per expense category.",
                "operationId": "getApiExpensesCategoryBudgets",
                "description": "Requires the `expenses:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "expenses:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/expenses/category-budgets/{category}": {
            "put": {
                "tags": [
                    "Expenses & claims"
                ],
                "summary": "Set a category's monthly budget.",
                "operationId": "putApiExpensesCategoryBudgetsByCategory",
                "description": "Requires the `expenses:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "expenses:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "category",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "monthlyBudget": {
                                        "type": "number"
                                    }
                                }
                            },
                            "example": {
                                "monthlyBudget": 15000.0
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/documents/ocr": {
            "get": {
                "tags": [
                    "Expenses & claims"
                ],
                "summary": "Documents already scanned, with per-field confidence, provenance and raw text.",
                "operationId": "getApiDocumentsOcr",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Expenses & claims"
                ],
                "summary": "Scan a receipt, supplier invoice or statement — PDF, JPEG, PNG or multi-page TIFF. Unlike /api/expenses/ocr this returns confidence and provenance per field, not just values.",
                "operationId": "postApiDocumentsOcr",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/documents/inbox": {
            "get": {
                "tags": [
                    "Expenses & claims"
                ],
                "summary": "The document inbox: everything uploaded in bulk or emailed in, newest first, with what the reader made of each.",
                "operationId": "getApiDocumentsInbox",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Expenses & claims"
                ],
                "summary": "Drop documents in. Multipart, several files at a time; answers immediately and reads them off the request.",
                "operationId": "postApiDocumentsInbox",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/documents/inbox/{id}": {
            "get": {
                "tags": [
                    "Expenses & claims"
                ],
                "summary": "One item's full reading, plus what this tenant's own history says to file it as.",
                "operationId": "getApiDocumentsInboxById",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/documents/inbox/{id}/retry": {
            "post": {
                "tags": [
                    "Expenses & claims"
                ],
                "summary": "Put a failed item back in the reading queue.",
                "operationId": "postApiDocumentsInboxByIdRetry",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/documents/inbox/{id}/unsplit": {
            "post": {
                "tags": [
                    "Expenses & claims"
                ],
                "summary": "Put a page that was split into several documents back together: the crops are discarded and the whole page is read again as one.",
                "operationId": "postApiDocumentsInboxByIdUnsplit",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/documents/inbox/{id}/discard": {
            "post": {
                "tags": [
                    "Expenses & claims"
                ],
                "summary": "Throw an inbox item away without filing it.",
                "operationId": "postApiDocumentsInboxByIdDiscard",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/documents/inbox/{id}/import-statement": {
            "post": {
                "tags": [
                    "Expenses & claims"
                ],
                "summary": "Import an emailed bank statement into the account you choose. Held statements are the ones that read perfectly but could not prove which account they belong to, or did not add up to the closing balance the bank printed. Gated on banking:create, not expenses — this writes bank transactions.",
                "operationId": "postApiDocumentsInboxByIdImportStatement",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "bankAccountId": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "bankAccountId": "<uuid>"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/documents/inbox/{id}/link": {
            "post": {
                "tags": [
                    "Expenses & claims"
                ],
                "summary": "Attach an inbox item to the expense or bill it became, and remember how this supplier gets filed.",
                "operationId": "postApiDocumentsInboxByIdLink",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "expenseId": {
                                        "type": "string"
                                    },
                                    "category": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "expenseId": "<uuid>",
                                "category": "fuel"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/documents/inbox/address": {
            "get": {
                "tags": [
                    "Expenses & claims"
                ],
                "summary": "This company's email-in address. Minted on first ask; absent when the deployment cannot receive email.",
                "operationId": "getApiDocumentsInboxAddress",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/documents/inbox/address/rotate": {
            "post": {
                "tags": [
                    "Expenses & claims"
                ],
                "summary": "Issue a new email-in address. The previous one stops working immediately.",
                "operationId": "postApiDocumentsInboxAddressRotate",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/inbound/email/{secret}": {
            "post": {
                "tags": [
                    "Expenses & claims"
                ],
                "summary": "Mailjet Parse API webhook: a forwarded supplier invoice becomes an inbox item. Registered only when LEDGR_INBOUND_EMAIL_SECRET is set.",
                "operationId": "postInboundEmailBySecret",
                "security": [],
                "parameters": [
                    {
                        "name": "secret",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    }
                }
            }
        },
        "/api/documents/ocr/{id}/link": {
            "post": {
                "tags": [
                    "Expenses & claims"
                ],
                "summary": "Attach a scan to the expense or bill it turned into.",
                "operationId": "postApiDocumentsOcrByIdLink",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "expenseId": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "expenseId": "<uuid>"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/bills": {
            "get": {
                "tags": [
                    "Expenses & claims"
                ],
                "summary": "Supplier invoices.",
                "operationId": "getApiBills",
                "description": "Requires the `expenses:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "expenses:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Expenses & claims"
                ],
                "summary": "Capture a supplier invoice.",
                "operationId": "postApiBills",
                "description": "Requires the `expenses:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "expenses:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "vendor": {
                                        "type": "string"
                                    },
                                    "issueDate": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "dueDate": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "amount": {
                                        "type": "number"
                                    },
                                    "billNumber": {
                                        "type": "string"
                                    },
                                    "lines": {
                                        "type": "array",
                                        "items": {
                                            "type": "object",
                                            "properties": {
                                                "description": {
                                                    "type": "string"
                                                },
                                                "quantity": {
                                                    "type": "number"
                                                },
                                                "unitPrice": {
                                                    "type": "number"
                                                },
                                                "vatRate": {
                                                    "type": "number"
                                                }
                                            }
                                        }
                                    }
                                }
                            },
                            "example": {
                                "vendor": "Example",
                                "issueDate": "2026-09-30",
                                "dueDate": "2026-09-30",
                                "amount": 1500.0,
                                "billNumber": "ACME-8841",
                                "lines": [
                                    {
                                        "description": "Steel angle 50x50",
                                        "quantity": 120.0,
                                        "unitPrice": 84.5,
                                        "vatRate": 15.0
                                    }
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/bills/{id}": {
            "get": {
                "tags": [
                    "Expenses & claims"
                ],
                "summary": "One bill with its lines and payments.",
                "operationId": "getApiBillsById",
                "description": "Requires the `expenses:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "expenses:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "put": {
                "tags": [
                    "Expenses & claims"
                ],
                "summary": "Update a bill.",
                "operationId": "putApiBillsById",
                "description": "Requires the `expenses:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "expenses:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "dueDate": {
                                        "type": "string",
                                        "format": "date"
                                    }
                                }
                            },
                            "example": {
                                "dueDate": "2026-09-15"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "delete": {
                "tags": [
                    "Expenses & claims"
                ],
                "summary": "Delete a bill.",
                "operationId": "deleteApiBillsById",
                "description": "Requires the `expenses:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "expenses:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/bills/{id}/pay": {
            "post": {
                "tags": [
                    "Expenses & claims"
                ],
                "summary": "Record a payment against a bill.",
                "operationId": "postApiBillsByIdPay",
                "description": "Requires the `expenses:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "expenses:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {}
                            },
                            "example": {}
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/mileage/trips": {
            "get": {
                "tags": [
                    "Expenses & claims"
                ],
                "summary": "Logged trips with their calculated claim.",
                "operationId": "getApiMileageTrips",
                "description": "Requires the `expenses:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "expenses:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Expenses & claims"
                ],
                "summary": "Log a trip. The rate is year-keyed on SarsTravelRates and resolved from the trip's own date — R4.95/km for 2026/27, R4.64/km for 2025/26.",
                "operationId": "postApiMileageTrips",
                "description": "Requires the `expenses:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "expenses:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "tripDate": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "origin": {
                                        "type": "string"
                                    },
                                    "destination": {
                                        "type": "string"
                                    },
                                    "distanceKm": {
                                        "type": "number"
                                    },
                                    "purpose": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "tripDate": "2026-09-30",
                                "origin": "Example",
                                "destination": "Example",
                                "distanceKm": 145.0,
                                "purpose": "Site inspection"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/mileage/trips/{id}": {
            "put": {
                "tags": [
                    "Expenses & claims"
                ],
                "summary": "Correct a trip.",
                "operationId": "putApiMileageTripsById",
                "description": "Requires the `expenses:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "expenses:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "distanceKm": {
                                        "type": "number"
                                    }
                                }
                            },
                            "example": {
                                "distanceKm": 64.0
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "delete": {
                "tags": [
                    "Expenses & claims"
                ],
                "summary": "Delete a trip.",
                "operationId": "deleteApiMileageTripsById",
                "description": "Requires the `expenses:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "expenses:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/per-diem/claims": {
            "get": {
                "tags": [
                    "Expenses & claims"
                ],
                "summary": "Travel allowance claims.",
                "operationId": "getApiPerDiemClaims",
                "description": "Requires the `expenses:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "expenses:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Expenses & claims"
                ],
                "summary": "Raise a per diem claim.",
                "operationId": "postApiPerDiemClaims",
                "description": "Requires the `expenses:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "expenses:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "employee": {
                                        "type": "string"
                                    },
                                    "destination": {
                                        "type": "string"
                                    },
                                    "periodLabel": {
                                        "type": "string"
                                    },
                                    "days": {
                                        "type": "integer"
                                    },
                                    "ratePerDay": {
                                        "type": "number"
                                    }
                                }
                            },
                            "example": {
                                "employee": "Example",
                                "destination": "Example",
                                "periodLabel": "Example",
                                "days": 30,
                                "ratePerDay": 15.0
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/per-diem/claims/{id}": {
            "put": {
                "tags": [
                    "Expenses & claims"
                ],
                "summary": "Update a claim.",
                "operationId": "putApiPerDiemClaimsById",
                "description": "Requires the `expenses:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "expenses:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "days": {
                                        "type": "integer"
                                    }
                                }
                            },
                            "example": {
                                "days": 3
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "delete": {
                "tags": [
                    "Expenses & claims"
                ],
                "summary": "Delete a claim.",
                "operationId": "deleteApiPerDiemClaimsById",
                "description": "Requires the `expenses:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "expenses:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/per-diem/claims/{id}/approve": {
            "post": {
                "tags": [
                    "Expenses & claims"
                ],
                "summary": "Approve a claim.",
                "operationId": "postApiPerDiemClaimsByIdApprove",
                "description": "Requires the `expenses:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "expenses:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/per-diem/claims/{id}/reject": {
            "post": {
                "tags": [
                    "Expenses & claims"
                ],
                "summary": "Reject a claim, with a reason.",
                "operationId": "postApiPerDiemClaimsByIdReject",
                "description": "Requires the `expenses:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "expenses:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "reason": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "reason": "Dates overlap an existing claim"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/per-diem/claims/{id}/pay": {
            "post": {
                "tags": [
                    "Expenses & claims"
                ],
                "summary": "Record payment of a claim.",
                "operationId": "postApiPerDiemClaimsByIdPay",
                "description": "Requires the `expenses:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "expenses:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "bankAccountId": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "bankAccountId": "<uuid>"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/petty-cash": {
            "get": {
                "tags": [
                    "Expenses & claims"
                ],
                "summary": "The float and its transactions.",
                "operationId": "getApiPettyCash",
                "description": "Requires the `expenses:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "expenses:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/petty-cash/float": {
            "put": {
                "tags": [
                    "Expenses & claims"
                ],
                "summary": "Set or top up the float.",
                "operationId": "putApiPettyCashFloat",
                "description": "Requires the `expenses:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "expenses:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "floatAmount": {
                                        "type": "number"
                                    }
                                }
                            },
                            "example": {
                                "floatAmount": 1500.0
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/petty-cash/txns": {
            "post": {
                "tags": [
                    "Expenses & claims"
                ],
                "summary": "Record a petty-cash payment or receipt.",
                "operationId": "postApiPettyCashTxns",
                "description": "Requires the `expenses:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "expenses:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "date": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "description": {
                                        "type": "string"
                                    },
                                    "amount": {
                                        "type": "number"
                                    },
                                    "category": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "date": "2026-08-19",
                                "description": "Courier",
                                "amount": 145.0,
                                "category": "Postage"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/petty-cash/txns/{id}": {
            "put": {
                "tags": [
                    "Expenses & claims"
                ],
                "summary": "Correct a petty-cash transaction.",
                "operationId": "putApiPettyCashTxnsById",
                "description": "Requires the `expenses:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "expenses:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "amount": {
                                        "type": "number"
                                    }
                                }
                            },
                            "example": {
                                "amount": 150.0
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "delete": {
                "tags": [
                    "Expenses & claims"
                ],
                "summary": "Delete a petty-cash transaction.",
                "operationId": "deleteApiPettyCashTxnsById",
                "description": "Requires the `expenses:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "expenses:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/expenses/{id}/reopen": {
            "post": {
                "tags": [
                    "Expenses & claims"
                ],
                "summary": "Un-pay a claim: PAID back to APPROVED, reversing the cash leg to the accrual. Needs expenses:approve, because it undoes a payment decision. Added with the settlement guard and required by it — refusing to edit a paid claim would otherwise be a dead end, as this module had no way back from PAID at all.",
                "operationId": "postApiExpensesByIdReopen",
                "description": "Requires the `expenses:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "expenses:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/mileage/trips/{id}/submit": {
            "post": {
                "tags": [
                    "Expenses & claims"
                ],
                "summary": "Submit a logged trip for approval. The claimant's own act, so expenses:create rather than approve.",
                "operationId": "postApiMileageTripsByIdSubmit",
                "description": "Requires the `expenses:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "expenses:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/mileage/trips/{id}/approve": {
            "post": {
                "tags": [
                    "Expenses & claims"
                ],
                "summary": "Approve a trip. Posts DR 6280 Mileage Reimbursement / CR 2120 Accruals at the cent-rounded claim. Needs expenses:approve — before these endpoints existed the only way to write a status was PUT, which took any string on expenses:edit.",
                "operationId": "postApiMileageTripsByIdApprove",
                "description": "Requires the `expenses:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "expenses:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/mileage/trips/{id}/reject": {
            "post": {
                "tags": [
                    "Expenses & claims"
                ],
                "summary": "Reject a trip, or take an approval back before the money goes out. Reverses any posting. Needs expenses:approve.",
                "operationId": "postApiMileageTripsByIdReject",
                "description": "Requires the `expenses:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "expenses:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/mileage/trips/{id}/pay": {
            "post": {
                "tags": [
                    "Expenses & claims"
                ],
                "summary": "Mark a trip reimbursed. Moves the accrual to the bank (DR 2120 / CR 1350). Only from approved, and terminal — a paid trip can no longer be edited. Needs expenses:approve.",
                "operationId": "postApiMileageTripsByIdPay",
                "description": "Requires the `expenses:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "expenses:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/purchase-orders": {
            "get": {
                "tags": [
                    "Purchasing & suppliers"
                ],
                "summary": "Purchase orders and the open commitment they represent.",
                "operationId": "getApiPurchaseOrders",
                "description": "Requires the `expenses:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "expenses:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/purchase-orders/{id}": {
            "get": {
                "tags": [
                    "Purchasing & suppliers"
                ],
                "summary": "One order with its lines and receipt history.",
                "operationId": "getApiPurchaseOrdersById",
                "description": "Requires the `expenses:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "expenses:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/purchase-orders/{id}/approve": {
            "post": {
                "tags": [
                    "Purchasing & suppliers"
                ],
                "summary": "Approve an order for transmission.",
                "operationId": "postApiPurchaseOrdersByIdApprove",
                "description": "Requires the `expenses:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "expenses:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/purchase-orders/{id}/cancel": {
            "post": {
                "tags": [
                    "Purchasing & suppliers"
                ],
                "summary": "Cancel an order and release its commitment.",
                "operationId": "postApiPurchaseOrdersByIdCancel",
                "description": "Requires the `expenses:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "expenses:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/purchase-orders/{id}/receipts": {
            "get": {
                "tags": [
                    "Purchasing & suppliers"
                ],
                "summary": "What has been received against this order.",
                "operationId": "getApiPurchaseOrdersByIdReceipts",
                "description": "Requires the `expenses:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "expenses:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/purchase-orders/{id}/receive": {
            "post": {
                "tags": [
                    "Purchasing & suppliers"
                ],
                "summary": "Receive goods. The stock movement is dated the RECEIPT date, not the day it was keyed — the journal already was, so a backdated capture used to put stock and the GL in different periods for one event.",
                "operationId": "postApiPurchaseOrdersByIdReceive",
                "description": "Requires the `expenses:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "expenses:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "receiptDate": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "lines": {
                                        "type": "array",
                                        "items": {
                                            "type": "object",
                                            "properties": {
                                                "lineId": {
                                                    "type": "string"
                                                },
                                                "qtyReceived": {
                                                    "type": "number"
                                                }
                                            }
                                        }
                                    },
                                    "locationId": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "receiptDate": "2026-09-30",
                                "lines": [
                                    {
                                        "lineId": "<uuid>",
                                        "qtyReceived": 2.0
                                    }
                                ],
                                "locationId": "<uuid>"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/landed-costs": {
            "get": {
                "tags": [
                    "Purchasing & suppliers"
                ],
                "summary": "Landed-cost dockets — freight, duty and clearing waiting to be apportioned into stock.",
                "operationId": "getApiLandedCosts",
                "description": "Requires the `inventory:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Purchasing & suppliers"
                ],
                "summary": "Open a docket against one or more goods receipts.",
                "operationId": "postApiLandedCosts",
                "description": "Requires the `inventory:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "docketNumber": {
                                        "type": "string"
                                    },
                                    "goodsReceiptIds": {
                                        "type": "array",
                                        "items": {
                                            "type": "string"
                                        }
                                    },
                                    "charges": {
                                        "type": "array",
                                        "items": {
                                            "type": "object",
                                            "properties": {
                                                "costType": {
                                                    "type": "string"
                                                },
                                                "amount": {
                                                    "type": "number"
                                                }
                                            }
                                        }
                                    }
                                }
                            },
                            "example": {
                                "docketNumber": "SHIP-2026-14",
                                "goodsReceiptIds": [
                                    "<uuid>"
                                ],
                                "charges": [
                                    {
                                        "costType": "freight",
                                        "amount": 18400.0
                                    }
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/landed-costs/{id}": {
            "get": {
                "tags": [
                    "Purchasing & suppliers"
                ],
                "summary": "One docket and its apportionment.",
                "operationId": "getApiLandedCostsById",
                "description": "Requires the `inventory:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "put": {
                "tags": [
                    "Purchasing & suppliers"
                ],
                "summary": "Change a docket before it is posted.",
                "operationId": "putApiLandedCostsById",
                "description": "Requires the `inventory:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "charges": {
                                        "type": "array",
                                        "items": {
                                            "type": "object",
                                            "properties": {
                                                "costType": {
                                                    "type": "string"
                                                },
                                                "amount": {
                                                    "type": "number"
                                                }
                                            }
                                        }
                                    }
                                }
                            },
                            "example": {
                                "charges": [
                                    {
                                        "costType": "duty",
                                        "amount": 9200.0
                                    }
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "delete": {
                "tags": [
                    "Purchasing & suppliers"
                ],
                "summary": "Discard an unposted docket.",
                "operationId": "deleteApiLandedCostsById",
                "description": "Requires the `inventory:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/landed-costs/{id}/post": {
            "post": {
                "tags": [
                    "Purchasing & suppliers"
                ],
                "summary": "Apportion the cost into the batches it brought in and raise the journal.",
                "operationId": "postApiLandedCostsByIdPost",
                "description": "Requires the `inventory:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/landed-costs/defaults": {
            "get": {
                "tags": [
                    "Purchasing & suppliers"
                ],
                "summary": "Standing cost types and their apportionment basis.",
                "operationId": "getApiLandedCostsDefaults",
                "description": "Requires the `inventory:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Purchasing & suppliers"
                ],
                "summary": "Add a cost type.",
                "operationId": "postApiLandedCostsDefaults",
                "description": "Requires the `inventory:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "chargeType": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "chargeType": "freight"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/landed-costs/defaults/{id}": {
            "delete": {
                "tags": [
                    "Purchasing & suppliers"
                ],
                "summary": "Remove a cost type.",
                "operationId": "deleteApiLandedCostsDefaultsById",
                "description": "Requires the `inventory:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/landed-costs/receipt-options": {
            "get": {
                "tags": [
                    "Purchasing & suppliers"
                ],
                "summary": "Goods receipts a docket can still be attached to.",
                "operationId": "getApiLandedCostsReceiptOptions",
                "description": "Requires the `inventory:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/suppliers/aged": {
            "get": {
                "tags": [
                    "Purchasing & suppliers"
                ],
                "summary": "Aged creditors by bucket.",
                "operationId": "getApiSuppliersAged",
                "description": "Requires the `expenses:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "expenses:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/suppliers/{id}/statement": {
            "get": {
                "tags": [
                    "Purchasing & suppliers"
                ],
                "summary": "A supplier statement as data.",
                "operationId": "getApiSuppliersByIdStatement",
                "description": "Requires the `expenses:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "expenses:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/suppliers/{id}/statement.pdf": {
            "get": {
                "tags": [
                    "Purchasing & suppliers"
                ],
                "summary": "The same statement as a PDF.",
                "operationId": "getApiSuppliersByIdStatement.pdf",
                "description": "Requires the `expenses:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "expenses:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/suppliers/{id}/send-statement": {
            "post": {
                "tags": [
                    "Purchasing & suppliers"
                ],
                "summary": "Send a remittance or statement to the supplier.",
                "operationId": "postApiSuppliersByIdSendStatement",
                "description": "Requires the `expenses:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "expenses:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "channel": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "channel": "email"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/suppliers/{id}/reconcile": {
            "post": {
                "tags": [
                    "Purchasing & suppliers"
                ],
                "summary": "Reconcile the supplier's own statement against what Ledgr holds, and report the differences.",
                "operationId": "postApiSuppliersByIdReconcile",
                "description": "Requires the `expenses:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "expenses:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "supplierBalance": {
                                        "type": "number"
                                    }
                                }
                            },
                            "example": {
                                "supplierBalance": 1500.0
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/bank-feeds": {
            "get": {
                "tags": [
                    "Banking & reconciliation"
                ],
                "summary": "This company's Investec feeds, plus whether a feed can be connected at all and how to get the keys.",
                "operationId": "getApiBankFeeds",
                "description": "Requires the `banking:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "banking:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Banking & reconciliation"
                ],
                "summary": "Connect a feed with your own Investec client id, secret and API key. They are proved against Investec before they are stored, and the reply carries the accounts they can see.",
                "operationId": "postApiBankFeeds",
                "description": "Requires the `banking:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "banking:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "clientId": {
                                        "type": "string"
                                    },
                                    "clientSecret": {
                                        "type": "string"
                                    },
                                    "apiKey": {
                                        "type": "string"
                                    },
                                    "syncFrom": {
                                        "type": "string",
                                        "format": "date"
                                    }
                                }
                            },
                            "example": {
                                "clientId": "<your Investec client id>",
                                "clientSecret": "<your Investec client secret>",
                                "apiKey": "<your Investec API key>",
                                "syncFrom": "2026-06-01"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/bank-feeds/{id}/accounts": {
            "get": {
                "tags": [
                    "Banking & reconciliation"
                ],
                "summary": "Re-read the Investec account list, with a suggested Ledgr account matched by account number.",
                "operationId": "getApiBankFeedsByIdAccounts",
                "description": "Requires the `banking:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "banking:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/bank-feeds/{id}/link": {
            "post": {
                "tags": [
                    "Banking & reconciliation"
                ],
                "summary": "Map an Investec account onto a Ledgr bank account. An unmapped feed imports nothing rather than guessing.",
                "operationId": "postApiBankFeedsByIdLink",
                "description": "Requires the `banking:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "banking:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "investecAccountId": {
                                        "type": "string"
                                    },
                                    "bankAccountId": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "investecAccountId": "<uuid>",
                                "bankAccountId": "<uuid>"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/bank-feeds/{id}/sync": {
            "post": {
                "tags": [
                    "Banking & reconciliation"
                ],
                "summary": "Pull now. Idempotent — Investec's own transaction id plus a content signature, so nothing imports twice.",
                "operationId": "postApiBankFeedsByIdSync",
                "description": "Requires the `banking:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "banking:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/bank-feeds/{id}/runs": {
            "get": {
                "tags": [
                    "Banking & reconciliation"
                ],
                "summary": "The sync history, newest first. A feed that silently stops is worse than no feed.",
                "operationId": "getApiBankFeedsByIdRuns",
                "description": "Requires the `banking:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "banking:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/bank-feeds/{id}": {
            "delete": {
                "tags": [
                    "Banking & reconciliation"
                ],
                "summary": "Disconnect and erase the stored Investec keys. Transactions already imported are kept.",
                "operationId": "deleteApiBankFeedsById",
                "description": "Requires the `banking:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "banking:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/bank-accounts": {
            "get": {
                "tags": [
                    "Banking & reconciliation"
                ],
                "summary": "Bank accounts with balances and sparkline trends.",
                "operationId": "getApiBankAccounts",
                "description": "Requires the `banking:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "banking:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Banking & reconciliation"
                ],
                "summary": "Add an account. Nine SA banks are supported for import.",
                "operationId": "postApiBankAccounts",
                "description": "Requires the `banking:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "banking:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string"
                                    },
                                    "type": {
                                        "type": "string"
                                    },
                                    "accountNumber": {
                                        "type": "string"
                                    },
                                    "currency": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "name": "Example",
                                "type": "cheque",
                                "accountNumber": "62811234567",
                                "currency": "ZAR"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/bank-accounts/{id}": {
            "get": {
                "tags": [
                    "Banking & reconciliation"
                ],
                "summary": "One account.",
                "operationId": "getApiBankAccountsById",
                "description": "Requires the `banking:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "banking:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "put": {
                "tags": [
                    "Banking & reconciliation"
                ],
                "summary": "Update an account.",
                "operationId": "putApiBankAccountsById",
                "description": "Requires the `banking:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "banking:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "name": "FNB Business Cheque (main)"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "delete": {
                "tags": [
                    "Banking & reconciliation"
                ],
                "summary": "Remove an account.",
                "operationId": "deleteApiBankAccountsById",
                "description": "Requires the `banking:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "banking:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/bank-transactions": {
            "get": {
                "tags": [
                    "Banking & reconciliation"
                ],
                "summary": "Transactions for an account.",
                "operationId": "getApiBankTransactions",
                "description": "Requires the `banking:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "banking:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Banking & reconciliation"
                ],
                "summary": "Key a transaction by hand.",
                "operationId": "postApiBankTransactions",
                "description": "Requires the `banking:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "banking:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "bankAccountId": {
                                        "type": "string"
                                    },
                                    "date": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "description": {
                                        "type": "string"
                                    },
                                    "amount": {
                                        "type": "number"
                                    }
                                }
                            },
                            "example": {
                                "bankAccountId": "<uuid>",
                                "date": "2026-08-14",
                                "description": "PAYFAST ACME",
                                "amount": 51750.0
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/bank-transactions/{id}": {
            "get": {
                "tags": [
                    "Banking & reconciliation"
                ],
                "summary": "One transaction and its match state.",
                "operationId": "getApiBankTransactionsById",
                "description": "Requires the `banking:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "banking:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "delete": {
                "tags": [
                    "Banking & reconciliation"
                ],
                "summary": "Delete a transaction.",
                "operationId": "deleteApiBankTransactionsById",
                "description": "Requires the `banking:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "banking:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/bank-transactions/import": {
            "post": {
                "tags": [
                    "Banking & reconciliation"
                ],
                "summary": "Import transactions already parsed into Ledgr's shape. TransactionDedupe matches a hand-keyed row against an uploaded one.",
                "operationId": "postApiBankTransactionsImport",
                "description": "Requires the `banking:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "banking:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "bankAccountId": {
                                        "type": "string"
                                    },
                                    "transactions": {
                                        "type": "array",
                                        "items": {
                                            "type": "object",
                                            "properties": {
                                                "date": {
                                                    "type": "string",
                                                    "format": "date"
                                                },
                                                "description": {
                                                    "type": "string"
                                                },
                                                "amount": {
                                                    "type": "number"
                                                }
                                            }
                                        }
                                    }
                                }
                            },
                            "example": {
                                "bankAccountId": "<uuid>",
                                "transactions": [
                                    {
                                        "date": "2026-08-14",
                                        "description": "PAYFAST ACME",
                                        "amount": 51750.0
                                    }
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/bank-transactions/{id}/match": {
            "post": {
                "tags": [
                    "Banking & reconciliation"
                ],
                "summary": "Match a transaction to an invoice or a bill.",
                "operationId": "postApiBankTransactionsByIdMatch",
                "description": "Requires the `banking:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "banking:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "invoiceId": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "invoiceId": "<uuid>"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/bank-transactions/{id}/unmatch": {
            "post": {
                "tags": [
                    "Banking & reconciliation"
                ],
                "summary": "Break a match.",
                "operationId": "postApiBankTransactionsByIdUnmatch",
                "description": "Requires the `banking:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "banking:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/bank-statements/profiles": {
            "get": {
                "tags": [
                    "Banking & reconciliation"
                ],
                "summary": "Column-mapping profiles for the nine supported banks, plus any the tenant has saved.",
                "operationId": "getApiBankStatementsProfiles",
                "description": "Requires the `banking:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "banking:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/bank-statements/preview": {
            "post": {
                "tags": [
                    "Banking & reconciliation"
                ],
                "summary": "Parse an uploaded CSV, OFX or QIF and show what would be imported, without writing.",
                "operationId": "postApiBankStatementsPreview",
                "description": "Requires the `banking:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "banking:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/bank-statements/convert": {
            "post": {
                "tags": [
                    "Banking & reconciliation"
                ],
                "summary": "Turn the PDF or scan your bank sent into a statement. Multipart upload; answers with the transactions, whether they reconcile to the bank's own closing balance, and the text to post to /import.",
                "operationId": "postApiBankStatementsConvert",
                "description": "Requires the `banking:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "banking:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/bank-statements/import": {
            "post": {
                "tags": [
                    "Banking & reconciliation"
                ],
                "summary": "Commit a parsed statement.",
                "operationId": "postApiBankStatementsImport",
                "description": "Requires the `banking:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "banking:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "bankAccountId": {
                                        "type": "string"
                                    },
                                    "profile": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "bankAccountId": "<uuid>",
                                "profile": "fnb-csv"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/bank-statements/export": {
            "get": {
                "tags": [
                    "Banking & reconciliation"
                ],
                "summary": "Export transactions back out as CSV.",
                "operationId": "getApiBankStatementsExport",
                "description": "Requires the `banking:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "banking:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/bank-rules": {
            "get": {
                "tags": [
                    "Banking & reconciliation"
                ],
                "summary": "Auto-categorisation rules.",
                "operationId": "getApiBankRules",
                "description": "Requires the `banking:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "banking:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Banking & reconciliation"
                ],
                "summary": "Create a rule.",
                "operationId": "postApiBankRules",
                "description": "Requires the `banking:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "banking:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "name": "Example"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/bank-rules/{id}": {
            "get": {
                "tags": [
                    "Banking & reconciliation"
                ],
                "summary": "One rule.",
                "operationId": "getApiBankRulesById",
                "description": "Requires the `banking:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "banking:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "put": {
                "tags": [
                    "Banking & reconciliation"
                ],
                "summary": "Update a rule.",
                "operationId": "putApiBankRulesById",
                "description": "Requires the `banking:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "banking:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {}
                            },
                            "example": {}
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "delete": {
                "tags": [
                    "Banking & reconciliation"
                ],
                "summary": "Delete a rule.",
                "operationId": "deleteApiBankRulesById",
                "description": "Requires the `banking:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "banking:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/bank-rules/preview": {
            "post": {
                "tags": [
                    "Banking & reconciliation"
                ],
                "summary": "Show what the rules would do to the unreconciled transactions, before doing it.",
                "operationId": "postApiBankRulesPreview",
                "description": "Requires the `banking:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "banking:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/bank-rules/apply": {
            "post": {
                "tags": [
                    "Banking & reconciliation"
                ],
                "summary": "Apply the rules, creating the transactions they imply.",
                "operationId": "postApiBankRulesApply",
                "description": "Requires the `banking:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "banking:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "bankAccountId": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "bankAccountId": "<uuid>"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/reconciliation": {
            "get": {
                "tags": [
                    "Banking & reconciliation"
                ],
                "summary": "Reconciliation sessions and their status ring.",
                "operationId": "getApiReconciliation",
                "description": "Requires the `banking:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "banking:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/reconciliation/start": {
            "post": {
                "tags": [
                    "Banking & reconciliation"
                ],
                "summary": "Open a reconciliation to a statement date and closing balance.",
                "operationId": "postApiReconciliationStart",
                "description": "Requires the `banking:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "banking:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "bankAccountId": {
                                        "type": "string"
                                    },
                                    "statementBalance": {
                                        "type": "number"
                                    }
                                }
                            },
                            "example": {
                                "bankAccountId": "<uuid>",
                                "statementBalance": 1500.0
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/reconciliation/{id}": {
            "get": {
                "tags": [
                    "Banking & reconciliation"
                ],
                "summary": "One session — cleared, uncleared and the difference still to explain.",
                "operationId": "getApiReconciliationById",
                "description": "Requires the `banking:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "banking:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/reconciliation/{id}/clear/{transactionId}": {
            "post": {
                "tags": [
                    "Banking & reconciliation"
                ],
                "summary": "Tick a transaction as appearing on the statement.",
                "operationId": "postApiReconciliationByIdClearByTransactionId",
                "description": "Requires the `banking:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "banking:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    },
                    {
                        "name": "transactionId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/reconciliation/{id}/unclear/{transactionId}": {
            "post": {
                "tags": [
                    "Banking & reconciliation"
                ],
                "summary": "Untick it.",
                "operationId": "postApiReconciliationByIdUnclearByTransactionId",
                "description": "Requires the `banking:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "banking:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    },
                    {
                        "name": "transactionId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/reconciliation/{id}/finalise": {
            "post": {
                "tags": [
                    "Banking & reconciliation"
                ],
                "summary": "Close the reconciliation. Refused while the difference is non-zero.",
                "operationId": "postApiReconciliationByIdFinalise",
                "description": "Requires the `banking:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "banking:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/reconciliation/{id}/reopen": {
            "post": {
                "tags": [
                    "Banking & reconciliation"
                ],
                "summary": "Reopen a finalised reconciliation.",
                "operationId": "postApiReconciliationByIdReopen",
                "description": "Requires the `banking:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "banking:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/accounts": {
            "get": {
                "tags": [
                    "General ledger"
                ],
                "summary": "The chart of accounts.",
                "operationId": "getApiAccounts",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "General ledger"
                ],
                "summary": "Add an account.",
                "operationId": "postApiAccounts",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "code": {
                                        "type": "string"
                                    },
                                    "name": {
                                        "type": "string"
                                    },
                                    "accountType": {
                                        "type": "string"
                                    },
                                    "normalBalance": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "code": "SKU-1001",
                                "name": "Example",
                                "accountType": "expense",
                                "normalBalance": "Example"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/accounts/tree": {
            "get": {
                "tags": [
                    "General ledger"
                ],
                "summary": "The chart as a tree, for the picker.",
                "operationId": "getApiAccountsTree",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/accounts/{id}": {
            "get": {
                "tags": [
                    "General ledger"
                ],
                "summary": "One account with its balance.",
                "operationId": "getApiAccountsById",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "put": {
                "tags": [
                    "General ledger"
                ],
                "summary": "Rename or recode an account.",
                "operationId": "putApiAccountsById",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "name": "Stock write-offs and shrinkage"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "delete": {
                "tags": [
                    "General ledger"
                ],
                "summary": "Delete an account. Refused while journal lines, invoice lines or quote lines reference it — which is why the Quotes tables survive the route being unmounted.",
                "operationId": "deleteApiAccountsById",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/ledger/journals": {
            "get": {
                "tags": [
                    "General ledger"
                ],
                "summary": "Manual journals.",
                "operationId": "getApiLedgerJournals",
                "description": "Requires the `reports:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "reports:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "General ledger"
                ],
                "summary": "Post a manual journal. Refused unless it balances and the period is open.",
                "operationId": "postApiLedgerJournals",
                "description": "Requires the `reports:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "reports:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "journalDate": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "memo": {
                                        "type": "string"
                                    },
                                    "lines": {
                                        "type": "array",
                                        "items": {
                                            "type": "object",
                                            "properties": {
                                                "accountId": {
                                                    "type": "string"
                                                },
                                                "debit": {
                                                    "type": "number"
                                                },
                                                "credit": {
                                                    "type": "number"
                                                }
                                            }
                                        }
                                    },
                                    "reference": {
                                        "type": "string"
                                    },
                                    "post": {
                                        "type": "boolean"
                                    }
                                }
                            },
                            "example": {
                                "journalDate": "2026-08-31",
                                "memo": "Depreciation for August",
                                "lines": [
                                    {
                                        "accountId": "<uuid>",
                                        "debit": 4166.67,
                                        "credit": 0.0
                                    },
                                    {
                                        "accountId": "<uuid>",
                                        "debit": 0.0,
                                        "credit": 4166.67
                                    }
                                ],
                                "reference": "JNL-0042",
                                "post": true
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/ledger/journals/{id}": {
            "get": {
                "tags": [
                    "General ledger"
                ],
                "summary": "One journal and its lines.",
                "operationId": "getApiLedgerJournalsById",
                "description": "Requires the `reports:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "reports:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/ledger/journals/{id}/reverse": {
            "post": {
                "tags": [
                    "General ledger"
                ],
                "summary": "Post the contra. Never edits the original.",
                "operationId": "postApiLedgerJournalsByIdReverse",
                "description": "Requires the `reports:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "reports:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "date": {
                                        "type": "string",
                                        "format": "date"
                                    }
                                }
                            },
                            "example": {
                                "date": "2026-09-01"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/ledger/periods": {
            "get": {
                "tags": [
                    "General ledger"
                ],
                "summary": "Accounting periods and their lock state.",
                "operationId": "getApiLedgerPeriods",
                "description": "Requires the `reports:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "reports:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "General ledger"
                ],
                "summary": "Open a period.",
                "operationId": "postApiLedgerPeriods",
                "description": "Requires the `reports:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "reports:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "periodStart": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "periodEnd": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "label": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "periodStart": "2026-09-01",
                                "periodEnd": "2026-09-30",
                                "label": "September 2026"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/ledger/periods/{id}/status": {
            "put": {
                "tags": [
                    "General ledger"
                ],
                "summary": "Lock or unlock a period. A locked period refuses every posting, including automatic ones.",
                "operationId": "putApiLedgerPeriodsByIdStatus",
                "description": "Requires the `reports:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "reports:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "status": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "status": "locked"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/ledger/cost-centres": {
            "get": {
                "tags": [
                    "General ledger"
                ],
                "summary": "Cost centres.",
                "operationId": "getApiLedgerCostCentres",
                "description": "Requires the `reports:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "reports:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "General ledger"
                ],
                "summary": "Add a cost centre.",
                "operationId": "postApiLedgerCostCentres",
                "description": "Requires the `reports:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "reports:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "code": {
                                        "type": "string"
                                    },
                                    "name": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "code": "CPT",
                                "name": "Cape Town branch"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/ledger/cost-centres/{id}": {
            "put": {
                "tags": [
                    "General ledger"
                ],
                "summary": "Rename a cost centre.",
                "operationId": "putApiLedgerCostCentresById",
                "description": "Requires the `reports:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "reports:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "code": {
                                        "type": "string"
                                    },
                                    "name": {
                                        "type": "string"
                                    },
                                    "kind": {
                                        "type": "string"
                                    },
                                    "isActive": {
                                        "type": "boolean"
                                    }
                                }
                            },
                            "example": {
                                "code": "CPT",
                                "name": "Cape Town Depot",
                                "kind": "branch",
                                "isActive": true
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "delete": {
                "tags": [
                    "General ledger"
                ],
                "summary": "Remove a cost centre.",
                "operationId": "deleteApiLedgerCostCentresById",
                "description": "Requires the `reports:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "reports:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/ledger/account-budgets": {
            "get": {
                "tags": [
                    "General ledger"
                ],
                "summary": "Budgets per ledger account and period.",
                "operationId": "getApiLedgerAccountBudgets",
                "description": "Requires the `reports:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "reports:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "put": {
                "tags": [
                    "General ledger"
                ],
                "summary": "Set a budget line.",
                "operationId": "putApiLedgerAccountBudgets",
                "description": "Requires the `reports:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "reports:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "budgets": {
                                        "type": "array",
                                        "items": {
                                            "type": "object",
                                            "properties": {
                                                "accountId": {
                                                    "type": "string"
                                                },
                                                "periodMonth": {
                                                    "type": "string"
                                                },
                                                "amount": {
                                                    "type": "number"
                                                },
                                                "costCentreId": {
                                                    "type": "string"
                                                },
                                                "notes": {
                                                    "type": "string"
                                                }
                                            }
                                        }
                                    }
                                }
                            },
                            "example": {
                                "budgets": [
                                    {
                                        "accountId": "<uuid>",
                                        "periodMonth": "2026-09",
                                        "amount": 45000.0,
                                        "costCentreId": "<uuid>",
                                        "notes": "Approved at the August board meeting"
                                    }
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/ledger/account-budgets/{id}": {
            "delete": {
                "tags": [
                    "General ledger"
                ],
                "summary": "Remove a budget line.",
                "operationId": "deleteApiLedgerAccountBudgetsById",
                "description": "Requires the `reports:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "reports:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/ledger/budget-by-account": {
            "get": {
                "tags": [
                    "General ledger"
                ],
                "summary": "Budget against actual, by account.",
                "operationId": "getApiLedgerBudgetByAccount",
                "description": "Requires the `reports:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "reports:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/ledger/fx-revaluations": {
            "get": {
                "tags": [
                    "General ledger"
                ],
                "summary": "Period-end FX revaluations already posted.",
                "operationId": "getApiLedgerFxRevaluations",
                "description": "Requires the `reports:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "reports:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/ledger/fx-revaluation": {
            "post": {
                "tags": [
                    "General ledger"
                ],
                "summary": "Revalue foreign-currency balances at the period-end rate and post the difference.",
                "operationId": "postApiLedgerFxRevaluation",
                "description": "Requires the `reports:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "reports:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "asOf": {
                                        "type": "string",
                                        "format": "date"
                                    }
                                }
                            },
                            "example": {
                                "asOf": "2026-08-31"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/ledger/health": {
            "get": {
                "tags": [
                    "General ledger"
                ],
                "summary": "Control-account checks — does the debtors control agree with the debtors ledger, does 1310 agree with the sum of open batch values.",
                "operationId": "getApiLedgerHealth",
                "description": "Requires the `reports:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "reports:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/ledger/rebuild": {
            "post": {
                "tags": [
                    "General ledger"
                ],
                "summary": "Rebuild the ledger from source documents. The repair of last resort, not a routine.",
                "operationId": "postApiLedgerRebuild",
                "description": "Requires the `reports:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "reports:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/ledger/seed-chart": {
            "post": {
                "tags": [
                    "General ledger"
                ],
                "summary": "Seed a standard SA chart of accounts into an empty tenant.",
                "operationId": "postApiLedgerSeedChart",
                "description": "Requires the `reports:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "reports:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/fx/rates": {
            "get": {
                "tags": [
                    "General ledger"
                ],
                "summary": "Exchange rates held, and how old each one is.",
                "operationId": "getApiFxRates",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "put": {
                "tags": [
                    "General ledger"
                ],
                "summary": "Record a rate by hand.",
                "operationId": "putApiFxRates",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "fromCurrency": {
                                        "type": "string"
                                    },
                                    "toCurrency": {
                                        "type": "string"
                                    },
                                    "rate": {
                                        "type": "number"
                                    }
                                }
                            },
                            "example": {
                                "fromCurrency": "ZAR",
                                "toCurrency": "ZAR",
                                "rate": 15.0
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/fx/refresh": {
            "post": {
                "tags": [
                    "General ledger"
                ],
                "summary": "Fetch the latest rates from the ECB.",
                "operationId": "postApiFxRefresh",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/fixed-assets": {
            "get": {
                "tags": [
                    "General ledger"
                ],
                "summary": "The asset register.",
                "operationId": "getApiFixedAssets",
                "description": "Requires the `reports:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "reports:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "General ledger"
                ],
                "summary": "Capitalise an asset against a SARS BGR7 class.",
                "operationId": "postApiFixedAssets",
                "description": "Requires the `reports:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "reports:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string"
                                    },
                                    "assetClass": {
                                        "type": "string"
                                    },
                                    "acquisitionDate": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "cost": {
                                        "type": "number"
                                    }
                                }
                            },
                            "example": {
                                "name": "Example",
                                "assetClass": "Example",
                                "acquisitionDate": "2026-09-30",
                                "cost": 1500.0
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/fixed-assets/classes": {
            "get": {
                "tags": [
                    "General ledger"
                ],
                "summary": "The SARS BGR7 wear-and-tear classes and their write-off periods.",
                "operationId": "getApiFixedAssetsClasses",
                "description": "Requires the `reports:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "reports:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/fixed-assets/{id}": {
            "get": {
                "tags": [
                    "General ledger"
                ],
                "summary": "One asset with its carrying value.",
                "operationId": "getApiFixedAssetsById",
                "description": "Requires the `reports:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "reports:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "put": {
                "tags": [
                    "General ledger"
                ],
                "summary": "Update an asset.",
                "operationId": "putApiFixedAssetsById",
                "description": "Requires the `reports:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "reports:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "name": "Toyota Hilux (CA 123-456)"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "delete": {
                "tags": [
                    "General ledger"
                ],
                "summary": "Remove an asset that was capitalised in error.",
                "operationId": "deleteApiFixedAssetsById",
                "description": "Requires the `reports:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "reports:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/fixed-assets/{id}/schedule": {
            "get": {
                "tags": [
                    "General ledger"
                ],
                "summary": "The depreciation schedule for one asset.",
                "operationId": "getApiFixedAssetsByIdSchedule",
                "description": "Requires the `reports:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "reports:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/fixed-assets/depreciation/run": {
            "post": {
                "tags": [
                    "General ledger"
                ],
                "summary": "Post the month's depreciation across the register.",
                "operationId": "postApiFixedAssetsDepreciationRun",
                "description": "Requires the `reports:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "reports:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "period": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "period": "2026-08"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/fixed-assets/{id}/dispose": {
            "post": {
                "tags": [
                    "General ledger"
                ],
                "summary": "Dispose of an asset and post the profit or loss.",
                "operationId": "postApiFixedAssetsByIdDispose",
                "description": "Requires the `reports:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "reports:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "disposalDate": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "proceeds": {
                                        "type": "number"
                                    }
                                }
                            },
                            "example": {
                                "disposalDate": "2026-09-30",
                                "proceeds": 310000.0
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/reports/summary": {
            "get": {
                "tags": [
                    "Reports & analytics"
                ],
                "summary": "The dashboard KPI row — cash, receivables, revenue MTD.",
                "operationId": "getApiReportsSummary",
                "description": "Requires the `dashboard:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "dashboard:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/reports/cash-forecast": {
            "get": {
                "tags": [
                    "Reports & analytics"
                ],
                "summary": "The cash runway ring's figures.",
                "operationId": "getApiReportsCashForecast",
                "description": "Requires the `dashboard:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "dashboard:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/reports/income-statement": {
            "get": {
                "tags": [
                    "Reports & analytics"
                ],
                "summary": "Income statement (IFRS for SMEs).",
                "operationId": "getApiReportsIncomeStatement",
                "description": "Requires the `reports:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "reports:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/reports/balance-sheet": {
            "get": {
                "tags": [
                    "Reports & analytics"
                ],
                "summary": "Balance sheet at a date.",
                "operationId": "getApiReportsBalanceSheet",
                "description": "Requires the `reports:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "reports:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/reports/cash-flow": {
            "get": {
                "tags": [
                    "Reports & analytics"
                ],
                "summary": "Direct-method cash flow statement.",
                "operationId": "getApiReportsCashFlow",
                "description": "Requires the `reports:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "reports:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/reports/trial-balance": {
            "get": {
                "tags": [
                    "Reports & analytics"
                ],
                "summary": "Trial balance. Accepts ?costCentreId=<uuid> or the literal ?costCentreId=unallocated to restrict it to one branch, department or project; anything else is refused rather than widened to the whole company.",
                "operationId": "getApiReportsTrialBalance",
                "description": "Requires the `reports:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "reports:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/reports/general-ledger": {
            "get": {
                "tags": [
                    "Reports & analytics"
                ],
                "summary": "General ledger detail for an account. Takes the same ?costCentreId= filter as the trial balance, brought-forward balance included.",
                "operationId": "getApiReportsGeneralLedger",
                "description": "Requires the `reports:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "reports:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/reports/income-by-cost-centre": {
            "get": {
                "tags": [
                    "Reports & analytics"
                ],
                "summary": "The income statement with one column per cost centre — the departmental or branch P&L, for one legal entity with several branches. Every column and the total come from one read of journal_lines through the one income-statement classifier, and the response says whether the columns reconcile to the company total rather than leaving the reader to add them up.",
                "operationId": "getApiReportsIncomeByCostCentre",
                "description": "Requires the `reports:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "reports:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/reports/drill-down/{accountId}": {
            "get": {
                "tags": [
                    "Reports & analytics"
                ],
                "summary": "The transactions behind a figure, for click-through from a chart.",
                "operationId": "getApiReportsDrillDownByAccountId",
                "description": "Requires the `reports:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "reports:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "accountId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/reports/aged-debtors": {
            "get": {
                "tags": [
                    "Reports & analytics"
                ],
                "summary": "Aged debtors with the per-bucket breakdown the chart draws.",
                "operationId": "getApiReportsAgedDebtors",
                "description": "Requires the `reports:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "reports:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/reports/aged-creditors": {
            "get": {
                "tags": [
                    "Reports & analytics"
                ],
                "summary": "Aged creditors.",
                "operationId": "getApiReportsAgedCreditors",
                "description": "Requires the `reports:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "reports:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/reports/budget-vs-actual": {
            "get": {
                "tags": [
                    "Reports & analytics"
                ],
                "summary": "Budget against actual with variance.",
                "operationId": "getApiReportsBudgetVsActual",
                "description": "Requires the `reports:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "reports:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/reports/vat-analysis": {
            "get": {
                "tags": [
                    "Reports & analytics"
                ],
                "summary": "VAT by rate and by account, which is what a VAT201 is reconciled against.",
                "operationId": "getApiReportsVatAnalysis",
                "description": "Requires the `reports:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "reports:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/reports/bbbee": {
            "get": {
                "tags": [
                    "Reports & analytics"
                ],
                "summary": "The B-BBEE scorecard — six elements and the resulting level.",
                "operationId": "getApiReportsBbbee",
                "description": "Requires the `reports:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "reports:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "put": {
                "tags": [
                    "Reports & analytics"
                ],
                "summary": "Record the inputs the scorecard cannot derive from the books.",
                "operationId": "putApiReportsBbbee",
                "description": "Requires the `reports:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "reports:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "elements": {
                                        "type": "array",
                                        "items": {
                                            "type": "object",
                                            "properties": {
                                                "element": {
                                                    "type": "string"
                                                },
                                                "score": {
                                                    "type": "integer"
                                                }
                                            }
                                        }
                                    }
                                }
                            },
                            "example": {
                                "elements": [
                                    {
                                        "element": "ownership",
                                        "score": 18
                                    },
                                    {
                                        "element": "skills_development",
                                        "score": 11
                                    }
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/reports/scheduled": {
            "get": {
                "tags": [
                    "Reports & analytics"
                ],
                "summary": "Scheduled report subscriptions.",
                "operationId": "getApiReportsScheduled",
                "description": "Requires the `reports:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "reports:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Reports & analytics"
                ],
                "summary": "Schedule a report.",
                "operationId": "postApiReportsScheduled",
                "description": "Requires the `reports:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "reports:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string"
                                    },
                                    "reportType": {
                                        "type": "string"
                                    },
                                    "frequency": {
                                        "type": "string"
                                    },
                                    "nextRun": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "recipients": {
                                        "type": "string"
                                    },
                                    "format": {
                                        "type": "string"
                                    },
                                    "active": {
                                        "type": "boolean"
                                    }
                                }
                            },
                            "example": {
                                "name": "Monthly management pack",
                                "reportType": "income-statement",
                                "frequency": "monthly",
                                "nextRun": "2026-09-01",
                                "recipients": "andy@example.co.za",
                                "format": "pdf",
                                "active": true
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/reports/scheduled/{id}": {
            "put": {
                "tags": [
                    "Reports & analytics"
                ],
                "summary": "Change or pause a schedule.",
                "operationId": "putApiReportsScheduledById",
                "description": "Requires the `reports:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "reports:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "active": {
                                        "type": "boolean"
                                    }
                                }
                            },
                            "example": {
                                "active": false
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "delete": {
                "tags": [
                    "Reports & analytics"
                ],
                "summary": "Remove a schedule.",
                "operationId": "deleteApiReportsScheduledById",
                "description": "Requires the `reports:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "reports:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/reports/afs": {
            "get": {
                "tags": [
                    "Reports & analytics"
                ],
                "summary": "The annual financial statement set — cover page, responsibility statement, four statements with comparatives and disclosure-driven notes.",
                "operationId": "getApiReportsAfs",
                "description": "Requires the `reports:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "reports:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/reports/afs/years": {
            "get": {
                "tags": [
                    "Reports & analytics"
                ],
                "summary": "Which years an AFS set can be produced for.",
                "operationId": "getApiReportsAfsYears",
                "description": "Requires the `reports:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "reports:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/reports/afs/settings": {
            "get": {
                "tags": [
                    "Reports & analytics"
                ],
                "summary": "AFS presentation settings and the Companies Act public interest score inputs.",
                "operationId": "getApiReportsAfsSettings",
                "description": "Requires the `reports:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "reports:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "put": {
                "tags": [
                    "Reports & analytics"
                ],
                "summary": "Change the AFS settings.",
                "operationId": "putApiReportsAfsSettings",
                "description": "Requires the `reports:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "reports:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "preparedBy": {
                                        "type": "string"
                                    },
                                    "rounding": {
                                        "type": "integer"
                                    }
                                }
                            },
                            "example": {
                                "preparedBy": "Cameron & Co",
                                "rounding": 1
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/reports/afs/lead-schedules": {
            "get": {
                "tags": [
                    "Reports & analytics"
                ],
                "summary": "The lead schedules behind each statement line.",
                "operationId": "getApiReportsAfsLeadSchedules",
                "description": "Requires the `reports:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "reports:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/report-tables": {
            "get": {
                "tags": [
                    "Reports & analytics"
                ],
                "summary": "The flat, refreshable tables Excel can bind to. Accepts an API KEY directly as well as a JWT, because Power Query cannot run a token exchange and a monthly refresh cannot hold an hourly token.",
                "operationId": "getApiReportTables",
                "security": [
                    {
                        "apiKey": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/report-tables/{report}": {
            "get": {
                "tags": [
                    "Reports & analytics"
                ],
                "summary": "One table as JSON or CSV. Read-only, scope- and licence-checked explicitly.",
                "operationId": "getApiReportTablesByReport",
                "security": [
                    {
                        "apiKey": []
                    }
                ],
                "parameters": [
                    {
                        "name": "report",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/sars/overview": {
            "get": {
                "tags": [
                    "SARS returns"
                ],
                "summary": "Per-tax-type filing state — last filed and next due.",
                "operationId": "getApiSarsOverview",
                "description": "Requires the `sars:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "sars:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/sars/documents": {
            "get": {
                "tags": [
                    "SARS returns"
                ],
                "summary": "Which returns can be produced, and for which periods.",
                "operationId": "getApiSarsDocuments",
                "description": "Requires the `sars:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "sars:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/sars/documents/vat201": {
            "get": {
                "tags": [
                    "SARS returns"
                ],
                "summary": "The VAT201. Output-tax supplies come from one owner — OutputTaxSupplies in shared — because omitting credit notes, debit notes and deposits once declared a refund of R6 353.49 where R6 336.81 was payable.",
                "operationId": "getApiSarsDocumentsVat201",
                "description": "Requires the `sars:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "sars:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/sars/documents/emp201": {
            "get": {
                "tags": [
                    "SARS returns"
                ],
                "summary": "The EMP201 — PAYE, UIF, SDL and the ETI set off against them.",
                "operationId": "getApiSarsDocumentsEmp201",
                "description": "Requires the `sars:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "sars:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/sars/documents/emp501": {
            "get": {
                "tags": [
                    "SARS returns"
                ],
                "summary": "The EMP501 bi-annual reconciliation.",
                "operationId": "getApiSarsDocumentsEmp501",
                "description": "Requires the `sars:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "sars:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/sars/documents/irp5": {
            "get": {
                "tags": [
                    "SARS returns"
                ],
                "summary": "IRP5/IT3(a) certificates, reviewed per employee before generation — e@syFile rejects a whole import on one bad record.",
                "operationId": "getApiSarsDocumentsIrp5",
                "description": "Requires the `sars:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "sars:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/sars/documents/itr14": {
            "get": {
                "tags": [
                    "SARS returns"
                ],
                "summary": "The ITR14. The account-type alias is folded once, in Itr14TrialBalanceLine's constructor, because a Xero-shaped imported chart once declared nil turnover.",
                "operationId": "getApiSarsDocumentsItr14",
                "description": "Requires the `sars:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "sars:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/sars/documents/itr14/trial-balance": {
            "get": {
                "tags": [
                    "SARS returns"
                ],
                "summary": "The trial-balance CSV that ships as the ITR14's supporting schedule.",
                "operationId": "getApiSarsDocumentsItr14TrialBalance",
                "description": "Requires the `sars:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "sars:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/sars/documents/irp6": {
            "get": {
                "tags": [
                    "SARS returns"
                ],
                "summary": "The IRP6 provisional tax return.",
                "operationId": "getApiSarsDocumentsIrp6",
                "description": "Requires the `sars:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "sars:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/sars/documents/dividends": {
            "get": {
                "tags": [
                    "SARS returns"
                ],
                "summary": "Dividend declarations behind the DTR01/DTR02.",
                "operationId": "getApiSarsDocumentsDividends",
                "description": "Requires the `sars:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "sars:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "SARS returns"
                ],
                "summary": "Record a dividend declaration.",
                "operationId": "postApiSarsDocumentsDividends",
                "description": "Requires the `sars:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "sars:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "declarationDate": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "paymentDate": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "dividendDeclared": {
                                        "type": "number"
                                    },
                                    "reference": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "declarationDate": "2026-08-15",
                                "paymentDate": "2026-08-31",
                                "dividendDeclared": 250000.0,
                                "reference": "DIV-2026-01"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/sars/documents/dividends/{id}": {
            "delete": {
                "tags": [
                    "SARS returns"
                ],
                "summary": "Remove a declaration.",
                "operationId": "deleteApiSarsDocumentsDividendsById",
                "description": "Requires the `sars:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "sars:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/sars/documents/dtr01/{declarationId}": {
            "get": {
                "tags": [
                    "SARS returns"
                ],
                "summary": "The DTR01 for one declaration.",
                "operationId": "getApiSarsDocumentsDtr01ByDeclarationId",
                "description": "Requires the `sars:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "sars:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "declarationId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/sars/vat201": {
            "get": {
                "tags": [
                    "SARS returns"
                ],
                "summary": "The computed VAT201 boxes, as the screen renders them.",
                "operationId": "getApiSarsVat201",
                "description": "Requires the `sars:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "sars:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/sars/vat201/submit": {
            "post": {
                "tags": [
                    "SARS returns"
                ],
                "summary": "Note the VAT201 for a period as filed. The reference recorded is LEDGR'S OWN — nothing is transmitted to SARS.",
                "operationId": "postApiSarsVat201Submit",
                "description": "Requires the `sars:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "sars:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "periodStart": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "periodEnd": {
                                        "type": "string",
                                        "format": "date"
                                    }
                                }
                            },
                            "example": {
                                "periodStart": "2026-07-01",
                                "periodEnd": "2026-08-31"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/sars/it14": {
            "get": {
                "tags": [
                    "SARS returns"
                ],
                "summary": "The company income tax estimate for a fiscal year.",
                "operationId": "getApiSarsIt14",
                "description": "Requires the `sars:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "sars:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/sars/it14/submit": {
            "post": {
                "tags": [
                    "SARS returns"
                ],
                "summary": "Note the ITR14 as filed.",
                "operationId": "postApiSarsIt14Submit",
                "description": "Requires the `sars:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "sars:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "year": {
                                        "type": "integer"
                                    }
                                }
                            },
                            "example": {
                                "year": 2027
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/sars/irp6": {
            "get": {
                "tags": [
                    "SARS returns"
                ],
                "summary": "The provisional tax estimate for a period.",
                "operationId": "getApiSarsIrp6",
                "description": "Requires the `sars:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "sars:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/sars/irp6/submit": {
            "post": {
                "tags": [
                    "SARS returns"
                ],
                "summary": "Note the IRP6 as filed.",
                "operationId": "postApiSarsIrp6Submit",
                "description": "Requires the `sars:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "sars:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "year": {
                                        "type": "integer"
                                    },
                                    "period": {
                                        "type": "integer"
                                    }
                                }
                            },
                            "example": {
                                "year": 2027,
                                "period": 1
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/sars-submissions": {
            "get": {
                "tags": [
                    "SARS returns"
                ],
                "summary": "The filing register across every tax type.",
                "operationId": "getApiSarsSubmissions",
                "description": "Requires the `sars:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "sars:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "SARS returns"
                ],
                "summary": "Add a filing record by hand.",
                "operationId": "postApiSarsSubmissions",
                "description": "Requires the `sars:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "sars:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "submissionType": {
                                        "type": "string"
                                    },
                                    "periodStart": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "periodEnd": {
                                        "type": "string",
                                        "format": "date"
                                    }
                                }
                            },
                            "example": {
                                "submissionType": "VAT201",
                                "periodStart": "2026-07-01",
                                "periodEnd": "2026-08-31"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/sars-submissions/{id}": {
            "get": {
                "tags": [
                    "SARS returns"
                ],
                "summary": "One filing record.",
                "operationId": "getApiSarsSubmissionsById",
                "description": "Requires the `sars:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "sars:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "put": {
                "tags": [
                    "SARS returns"
                ],
                "summary": "Correct a filing record.",
                "operationId": "putApiSarsSubmissionsById",
                "description": "Requires the `sars:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "sars:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "submissionType": {
                                        "type": "string"
                                    },
                                    "periodStart": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "periodEnd": {
                                        "type": "string",
                                        "format": "date"
                                    }
                                }
                            },
                            "example": {
                                "submissionType": "VAT201",
                                "periodStart": "2026-07-01",
                                "periodEnd": "2026-08-31"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/sars-submissions/{id}/submit": {
            "post": {
                "tags": [
                    "SARS returns"
                ],
                "summary": "Mark a prepared return as filed.",
                "operationId": "postApiSarsSubmissionsByIdSubmit",
                "description": "Requires the `sars:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "sars:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/sars-submissions/emp201/{periodId}": {
            "get": {
                "tags": [
                    "SARS returns"
                ],
                "summary": "The EMP201 figures for a payroll period.",
                "operationId": "getApiSarsSubmissionsEmp201ByPeriodId",
                "description": "Requires the `sars:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "sars:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "periodId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/sars-submissions/emp501": {
            "get": {
                "tags": [
                    "SARS returns"
                ],
                "summary": "The EMP501 reconciliation figures.",
                "operationId": "getApiSarsSubmissionsEmp501",
                "description": "Requires the `sars:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "sars:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/sars/transmissions": {
            "get": {
                "tags": [
                    "SARS returns"
                ],
                "summary": "ISV direct filing. DORMANT — the whole tree is unregistered unless LEDGR_SARS_DIRECT_FILING is on, so these are a plain 404 by default and must never be surfaced in a client.",
                "operationId": "getApiSarsTransmissions",
                "description": "Requires the `sars:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "sars:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/sars/transmissions/isv": {
            "get": {
                "tags": [
                    "SARS returns"
                ],
                "summary": "ISV registration status. Dormant, as above.",
                "operationId": "getApiSarsTransmissionsIsv",
                "description": "Requires the `sars:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "sars:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/sars/transmissions/preview/{submissionId}": {
            "get": {
                "tags": [
                    "SARS returns"
                ],
                "summary": "Preview the envelope that would be transmitted. Dormant.",
                "operationId": "getApiSarsTransmissionsPreviewBySubmissionId",
                "description": "Requires the `sars:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "sars:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "submissionId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/sars/transmissions/{submissionId}": {
            "post": {
                "tags": [
                    "SARS returns"
                ],
                "summary": "Transmit a submission. Dormant.",
                "operationId": "postApiSarsTransmissionsBySubmissionId",
                "description": "Requires the `sars:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "sars:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "submissionId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/sars/transmissions/{id}/detail": {
            "get": {
                "tags": [
                    "SARS returns"
                ],
                "summary": "A transmission's state. Dormant.",
                "operationId": "getApiSarsTransmissionsByIdDetail",
                "description": "Requires the `sars:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "sars:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/sars/transmissions/{id}/envelope": {
            "get": {
                "tags": [
                    "SARS returns"
                ],
                "summary": "The transmitted envelope. Dormant.",
                "operationId": "getApiSarsTransmissionsByIdEnvelope",
                "description": "Requires the `sars:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "sars:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/sars/transmissions/{id}/poll": {
            "post": {
                "tags": [
                    "SARS returns"
                ],
                "summary": "Poll SARS for an outcome. Dormant.",
                "operationId": "postApiSarsTransmissionsByIdPoll",
                "description": "Requires the `sars:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "sars:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/employees": {
            "get": {
                "tags": [
                    "Payroll"
                ],
                "summary": "Employees with their statutory fields.",
                "operationId": "getApiEmployees",
                "description": "Requires the `payroll:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "payroll:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Payroll"
                ],
                "summary": "Create an employee. default_charge_rate and default_cost_rate live on the user record and fill in job-card labour — null means \"not set\" rather than zero, and the reports say so rather than showing a flattering 100% margin.",
                "operationId": "postApiEmployees",
                "description": "Requires the `payroll:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "payroll:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "firstName": {
                                        "type": "string"
                                    },
                                    "lastName": {
                                        "type": "string"
                                    },
                                    "idNumber": {
                                        "type": "string"
                                    },
                                    "taxNumber": {
                                        "type": "string"
                                    },
                                    "email": {
                                        "type": "string"
                                    },
                                    "basicSalary": {
                                        "type": "number"
                                    },
                                    "startDate": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "uifLiable": {
                                        "type": "boolean"
                                    }
                                }
                            },
                            "example": {
                                "firstName": "Andy",
                                "lastName": "Cameron",
                                "idNumber": "8001015800087",
                                "taxNumber": "0000012345",
                                "email": "andy@example.co.za",
                                "basicSalary": 45000.0,
                                "startDate": "2026-03-01",
                                "uifLiable": true
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/employees/{id}": {
            "get": {
                "tags": [
                    "Payroll"
                ],
                "summary": "One employee.",
                "operationId": "getApiEmployeesById",
                "description": "Requires the `payroll:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "payroll:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "put": {
                "tags": [
                    "Payroll"
                ],
                "summary": "Update an employee.",
                "operationId": "putApiEmployeesById",
                "description": "Requires the `payroll:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "payroll:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "basicSalary": {
                                        "type": "number"
                                    }
                                }
                            },
                            "example": {
                                "basicSalary": 48000.0
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "delete": {
                "tags": [
                    "Payroll"
                ],
                "summary": "Terminate an employee.",
                "operationId": "deleteApiEmployeesById",
                "description": "Requires the `payroll:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "payroll:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/employees/{id}/payslips": {
            "get": {
                "tags": [
                    "Payroll"
                ],
                "summary": "One employee's payslip history.",
                "operationId": "getApiEmployeesByIdPayslips",
                "description": "Requires the `payroll:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "payroll:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/employees/{id}/leave-balances": {
            "get": {
                "tags": [
                    "Payroll"
                ],
                "summary": "BCEA balances — 15 days annual, 30 days sick over a three-year cycle, three days family responsibility.",
                "operationId": "getApiEmployeesByIdLeaveBalances",
                "description": "Requires the `payroll:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "payroll:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/employees/{id}/irp5": {
            "get": {
                "tags": [
                    "Payroll"
                ],
                "summary": "One employee's IRP5 as data.",
                "operationId": "getApiEmployeesByIdIrp5",
                "description": "Requires the `payroll:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "payroll:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/employees/{id}/irp5.pdf": {
            "get": {
                "tags": [
                    "Payroll"
                ],
                "summary": "The same certificate as a PDF.",
                "operationId": "getApiEmployeesByIdIrp5.pdf",
                "description": "Requires the `payroll:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "payroll:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/payroll-periods": {
            "get": {
                "tags": [
                    "Payroll"
                ],
                "summary": "Payroll periods and their state.",
                "operationId": "getApiPayrollPeriods",
                "description": "Requires the `payroll:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "payroll:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Payroll"
                ],
                "summary": "Open a period.",
                "operationId": "postApiPayrollPeriods",
                "description": "Requires the `payroll:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "payroll:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "periodType": {
                                        "type": "string"
                                    },
                                    "startDate": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "endDate": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "payDate": {
                                        "type": "string",
                                        "format": "date"
                                    }
                                }
                            },
                            "example": {
                                "periodType": "MONTHLY",
                                "startDate": "2026-09-01",
                                "endDate": "2026-09-30",
                                "payDate": "2026-09-25"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/payroll-periods/{id}": {
            "get": {
                "tags": [
                    "Payroll"
                ],
                "summary": "One period with its totals.",
                "operationId": "getApiPayrollPeriodsById",
                "description": "Requires the `payroll:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "payroll:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "put": {
                "tags": [
                    "Payroll"
                ],
                "summary": "Change a period's dates before it is run.",
                "operationId": "putApiPayrollPeriodsById",
                "description": "Requires the `payroll:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "payroll:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "periodType": {
                                        "type": "string"
                                    },
                                    "startDate": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "endDate": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "payDate": {
                                        "type": "string",
                                        "format": "date"
                                    }
                                }
                            },
                            "example": {
                                "periodType": "MONTHLY",
                                "startDate": "2026-09-01",
                                "endDate": "2026-09-30",
                                "payDate": "2026-09-26"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "delete": {
                "tags": [
                    "Payroll"
                ],
                "summary": "Delete an unrun period.",
                "operationId": "deleteApiPayrollPeriodsById",
                "description": "Requires the `payroll:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "payroll:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/payroll-periods/{id}/run": {
            "post": {
                "tags": [
                    "Payroll"
                ],
                "summary": "Calculate the period — PAYE, UIF at 1% capped at R177.12 each side, SDL at 1%, ETI, deductions and fringe benefits.",
                "operationId": "postApiPayrollPeriodsByIdRun",
                "description": "Requires the `payroll:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "payroll:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/payroll-periods/{id}/approve": {
            "post": {
                "tags": [
                    "Payroll"
                ],
                "summary": "Approve the run and lock the payslips.",
                "operationId": "postApiPayrollPeriodsByIdApprove",
                "description": "Requires the `payroll:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "payroll:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/payroll-periods/{id}/reopen": {
            "post": {
                "tags": [
                    "Payroll"
                ],
                "summary": "Reopen an approved period for correction.",
                "operationId": "postApiPayrollPeriodsByIdReopen",
                "description": "Requires the `payroll:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "payroll:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/payroll-periods/{id}/pay": {
            "post": {
                "tags": [
                    "Payroll"
                ],
                "summary": "Record the payment run and post the journal.",
                "operationId": "postApiPayrollPeriodsByIdPay",
                "description": "Requires the `payroll:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "payroll:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "bankAccountId": {
                                        "type": "string"
                                    },
                                    "paymentDate": {
                                        "type": "string",
                                        "format": "date"
                                    }
                                }
                            },
                            "example": {
                                "bankAccountId": "<uuid>",
                                "paymentDate": "2026-09-25"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/payroll-periods/{id}/payslips": {
            "get": {
                "tags": [
                    "Payroll"
                ],
                "summary": "Every payslip in a period.",
                "operationId": "getApiPayrollPeriodsByIdPayslips",
                "description": "Requires the `payroll:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "payroll:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/payroll-periods/{id}/journal": {
            "get": {
                "tags": [
                    "Payroll"
                ],
                "summary": "The journal the period posts, before or after posting.",
                "operationId": "getApiPayrollPeriodsByIdJournal",
                "description": "Requires the `payroll:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "payroll:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/payslips/{id}": {
            "get": {
                "tags": [
                    "Payroll"
                ],
                "summary": "One payslip, line by line.",
                "operationId": "getApiPayslipsById",
                "description": "Requires the `payroll:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "payroll:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "put": {
                "tags": [
                    "Payroll"
                ],
                "summary": "Adjust a payslip before the period is approved.",
                "operationId": "putApiPayslipsById",
                "description": "Requires the `payroll:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "payroll:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {}
                            },
                            "example": {}
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/payslips/{id}/pdf": {
            "get": {
                "tags": [
                    "Payroll"
                ],
                "summary": "The payslip as a PDF.",
                "operationId": "getApiPayslipsByIdPdf",
                "description": "Requires the `payroll:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "payroll:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/payslips/{id}/send": {
            "post": {
                "tags": [
                    "Payroll"
                ],
                "summary": "Send a payslip by email or WhatsApp.",
                "operationId": "postApiPayslipsByIdSend",
                "description": "Requires the `payroll:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "payroll:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "channel": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "channel": "whatsapp"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/payslips/bulk-approve": {
            "post": {
                "tags": [
                    "Payroll"
                ],
                "summary": "Approve many payslips at once. Approving one at a time is why approvals get skipped.",
                "operationId": "postApiPayslipsBulkApprove",
                "description": "Requires the `payroll:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "payroll:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "payslipIds": {
                                        "type": "array",
                                        "items": {
                                            "type": "string"
                                        }
                                    }
                                }
                            },
                            "example": {
                                "payslipIds": [
                                    "<uuid>",
                                    "<uuid>"
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/payroll/irp5": {
            "get": {
                "tags": [
                    "Payroll"
                ],
                "summary": "The full IRP5 certificate set for a tax year, per employee, with the validation state e@syFile will apply.",
                "operationId": "getApiPayrollIrp5",
                "description": "Requires the `payroll:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "payroll:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/payroll/easyfile": {
            "get": {
                "tags": [
                    "Payroll"
                ],
                "summary": "What the e@syFile export would contain, and any records that would fail it.",
                "operationId": "getApiPayrollEasyfile",
                "description": "Requires the `payroll:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "payroll:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/payroll/easyfile/import-file": {
            "get": {
                "tags": [
                    "Payroll"
                ],
                "summary": "The e@syFile Employer import file itself.",
                "operationId": "getApiPayrollEasyfileImportFile",
                "description": "Requires the `payroll:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "payroll:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/payroll/easyfile/emp501": {
            "get": {
                "tags": [
                    "Payroll"
                ],
                "summary": "The EMP501 schedule that goes with it.",
                "operationId": "getApiPayrollEasyfileEmp501",
                "description": "Requires the `payroll:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "payroll:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/payroll/periods/{periodId}/remittances": {
            "get": {
                "tags": [
                    "Payroll"
                ],
                "summary": "Third-party remittances due for a period — medical aid, provident fund, garnishees.",
                "operationId": "getApiPayrollPeriodsByPeriodIdRemittances",
                "description": "Requires the `payroll:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "payroll:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "periodId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/payroll/beneficiaries": {
            "get": {
                "tags": [
                    "Payroll"
                ],
                "summary": "Third-party beneficiaries deductions are paid over to.",
                "operationId": "getApiPayrollBeneficiaries",
                "description": "Requires the `payroll:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "payroll:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Payroll"
                ],
                "summary": "Add a beneficiary.",
                "operationId": "postApiPayrollBeneficiaries",
                "description": "Requires the `payroll:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "payroll:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string"
                                    },
                                    "beneficiaryType": {
                                        "type": "string"
                                    },
                                    "bankName": {
                                        "type": "string"
                                    },
                                    "bankAccountNumber": {
                                        "type": "string"
                                    },
                                    "bankBranchCode": {
                                        "type": "string"
                                    },
                                    "remittanceEmail": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "name": "Momentum Retirement Annuity",
                                "beneficiaryType": "retirement_fund",
                                "bankName": "FNB",
                                "bankAccountNumber": "62012345678",
                                "bankBranchCode": "250655",
                                "remittanceEmail": "contributions@momentum.co.za"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/payroll/beneficiaries/types": {
            "get": {
                "tags": [
                    "Payroll"
                ],
                "summary": "The beneficiary types and how each is treated for PAYE.",
                "operationId": "getApiPayrollBeneficiariesTypes",
                "description": "Requires the `payroll:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "payroll:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/payroll/beneficiaries/{id}": {
            "put": {
                "tags": [
                    "Payroll"
                ],
                "summary": "Update a beneficiary.",
                "operationId": "putApiPayrollBeneficiariesById",
                "description": "Requires the `payroll:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "payroll:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string"
                                    },
                                    "beneficiaryType": {
                                        "type": "string"
                                    },
                                    "bankAccountNumber": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "name": "Momentum Retirement Annuity",
                                "beneficiaryType": "retirement_fund",
                                "bankAccountNumber": "62099998888"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "delete": {
                "tags": [
                    "Payroll"
                ],
                "summary": "Remove a beneficiary.",
                "operationId": "deleteApiPayrollBeneficiariesById",
                "description": "Requires the `payroll:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "payroll:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/payroll/deductions": {
            "get": {
                "tags": [
                    "Payroll"
                ],
                "summary": "Employee deductions and their remittance schedules.",
                "operationId": "getApiPayrollDeductions",
                "description": "Requires the `payroll:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "payroll:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Payroll"
                ],
                "summary": "Add a deduction against an employee.",
                "operationId": "postApiPayrollDeductions",
                "description": "Requires the `payroll:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "payroll:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "employeeId": {
                                        "type": "string"
                                    },
                                    "beneficiaryId": {
                                        "type": "string"
                                    },
                                    "startDate": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "calculation": {
                                        "type": "string"
                                    },
                                    "employeeAmount": {
                                        "type": "number"
                                    },
                                    "employerAmount": {
                                        "type": "number"
                                    },
                                    "memberReference": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "employeeId": "<uuid>",
                                "beneficiaryId": "<uuid>",
                                "startDate": "2026-09-01",
                                "calculation": "FIXED",
                                "employeeAmount": 1500.0,
                                "employerAmount": 1500.0,
                                "memberReference": "MOM-4471"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/payroll/deductions/{id}": {
            "put": {
                "tags": [
                    "Payroll"
                ],
                "summary": "Change a deduction.",
                "operationId": "putApiPayrollDeductionsById",
                "description": "Requires the `payroll:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "payroll:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "employeeId": {
                                        "type": "string"
                                    },
                                    "beneficiaryId": {
                                        "type": "string"
                                    },
                                    "startDate": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "calculation": {
                                        "type": "string"
                                    },
                                    "employeeAmount": {
                                        "type": "number"
                                    }
                                }
                            },
                            "example": {
                                "employeeId": "<uuid>",
                                "beneficiaryId": "<uuid>",
                                "startDate": "2026-09-01",
                                "calculation": "FIXED",
                                "employeeAmount": 1800.0
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "delete": {
                "tags": [
                    "Payroll"
                ],
                "summary": "End a deduction.",
                "operationId": "deleteApiPayrollDeductionsById",
                "description": "Requires the `payroll:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "payroll:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/payroll/cost-to-company": {
            "post": {
                "tags": [
                    "Payroll"
                ],
                "summary": "Structure a package backwards from a cost-to-company figure.",
                "operationId": "postApiPayrollCostToCompany",
                "description": "Requires the `payroll:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "payroll:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "costToCompany": {
                                        "type": "number"
                                    },
                                    "employeeId": {
                                        "type": "string"
                                    },
                                    "employerContributions": {
                                        "type": "number"
                                    },
                                    "uifLiable": {
                                        "type": "boolean"
                                    },
                                    "sdlExempt": {
                                        "type": "boolean"
                                    },
                                    "apply": {
                                        "type": "boolean"
                                    }
                                }
                            },
                            "example": {
                                "costToCompany": 45000.0,
                                "employeeId": "<uuid>",
                                "employerContributions": 3200.0,
                                "uifLiable": true,
                                "sdlExempt": false,
                                "apply": false
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/payroll/fringe-benefits": {
            "get": {
                "tags": [
                    "Payroll"
                ],
                "summary": "Fringe benefits in force.",
                "operationId": "getApiPayrollFringeBenefits",
                "description": "Requires the `payroll:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "payroll:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Payroll"
                ],
                "summary": "Add a fringe benefit. The engine and the tables existed from V211; nothing could write a row without a SQL client until this route.",
                "operationId": "postApiPayrollFringeBenefits",
                "description": "Requires the `payroll:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "payroll:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "employeeId": {
                                        "type": "string"
                                    },
                                    "benefitType": {
                                        "type": "string"
                                    },
                                    "startDate": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "determinedValue": {
                                        "type": "number"
                                    },
                                    "maintenancePlanIncluded": {
                                        "type": "boolean"
                                    },
                                    "businessUseAtLeast80": {
                                        "type": "boolean"
                                    }
                                }
                            },
                            "example": {
                                "employeeId": "<uuid>",
                                "benefitType": "MOTOR_VEHICLE",
                                "startDate": "2026-09-01",
                                "determinedValue": 420000.0,
                                "maintenancePlanIncluded": true,
                                "businessUseAtLeast80": false
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/payroll/fringe-benefits/types": {
            "get": {
                "tags": [
                    "Payroll"
                ],
                "summary": "The benefit types and the Seventh Schedule rule each follows.",
                "operationId": "getApiPayrollFringeBenefitsTypes",
                "description": "Requires the `payroll:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "payroll:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/payroll/fringe-benefits/{id}": {
            "put": {
                "tags": [
                    "Payroll"
                ],
                "summary": "Change a fringe benefit.",
                "operationId": "putApiPayrollFringeBenefitsById",
                "description": "Requires the `payroll:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "payroll:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "employeeId": {
                                        "type": "string"
                                    },
                                    "benefitType": {
                                        "type": "string"
                                    },
                                    "startDate": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "determinedValue": {
                                        "type": "number"
                                    }
                                }
                            },
                            "example": {
                                "employeeId": "<uuid>",
                                "benefitType": "MOTOR_VEHICLE",
                                "startDate": "2026-09-01",
                                "determinedValue": 395000.0
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "delete": {
                "tags": [
                    "Payroll"
                ],
                "summary": "End a fringe benefit.",
                "operationId": "deleteApiPayrollFringeBenefitsById",
                "description": "Requires the `payroll:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "payroll:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/payroll/eti": {
            "get": {
                "tags": [
                    "Payroll"
                ],
                "summary": "The Employment Tax Incentive as a view — who qualifies, which of the 24 months they are in, and what may be taken off PAYE.",
                "operationId": "getApiPayrollEti",
                "description": "Requires the `payroll:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "payroll:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/payroll/eti/employees/{id}": {
            "put": {
                "tags": [
                    "Payroll"
                ],
                "summary": "Correct an employee's ETI eligibility or start month.",
                "operationId": "putApiPayrollEtiEmployeesById",
                "description": "Requires the `payroll:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "payroll:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "etiMonthsClaimedBeforeLedgr": {
                                        "type": "integer"
                                    }
                                }
                            },
                            "example": {
                                "etiMonthsClaimedBeforeLedgr": 6
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/payroll/take-on": {
            "get": {
                "tags": [
                    "Payroll"
                ],
                "summary": "Opening year-to-date figures for an employer that switched to Ledgr part-way through a tax year.",
                "operationId": "getApiPayrollTakeOn",
                "description": "Requires the `payroll:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "payroll:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/payroll/take-on/columns": {
            "get": {
                "tags": [
                    "Payroll"
                ],
                "summary": "The columns the take-on import expects.",
                "operationId": "getApiPayrollTakeOnColumns",
                "description": "Requires the `payroll:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "payroll:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/payroll/take-on/template": {
            "get": {
                "tags": [
                    "Payroll"
                ],
                "summary": "A CSV template for the take-on.",
                "operationId": "getApiPayrollTakeOnTemplate",
                "description": "Requires the `payroll:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "payroll:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/payroll/take-on/import": {
            "post": {
                "tags": [
                    "Payroll"
                ],
                "summary": "Import opening YTD figures.",
                "operationId": "postApiPayrollTakeOnImport",
                "description": "Requires the `payroll:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "payroll:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "taxYearEndYear": {
                                        "type": "integer"
                                    },
                                    "csvContent": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "taxYearEndYear": 2027,
                                "csvContent": "employeeCode,grossYtd,payeYtd\\nE001,240000,52000"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/payroll/take-on/{employeeId}": {
            "put": {
                "tags": [
                    "Payroll"
                ],
                "summary": "Set one employee's opening figures by hand.",
                "operationId": "putApiPayrollTakeOnByEmployeeId",
                "description": "Requires the `payroll:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "payroll:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "employeeId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "remuneration": {
                                        "type": "number"
                                    },
                                    "paye": {
                                        "type": "number"
                                    },
                                    "uifEmployee": {
                                        "type": "number"
                                    }
                                }
                            },
                            "example": {
                                "remuneration": 240000.0,
                                "paye": 52000.0,
                                "uifEmployee": 1416.96
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/payroll/change-requests": {
            "get": {
                "tags": [
                    "Payroll"
                ],
                "summary": "Change requests employees have raised from the self-service portal.",
                "operationId": "getApiPayrollChangeRequests",
                "description": "Requires the `payroll:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "payroll:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/payroll/change-requests/{id}/review": {
            "post": {
                "tags": [
                    "Payroll"
                ],
                "summary": "Approve or decline an employee's change request.",
                "operationId": "postApiPayrollChangeRequestsByIdReview",
                "description": "Requires the `payroll:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "payroll:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "note": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "note": "Bank confirmation letter checked against the account number."
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/payroll/employees/{id}/portal-link": {
            "get": {
                "tags": [
                    "Payroll"
                ],
                "summary": "Whether this employee has a self-service link, and when it was issued.",
                "operationId": "getApiPayrollEmployeesByIdPortalLink",
                "description": "Requires the `payroll:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "payroll:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Payroll"
                ],
                "summary": "Issue a self-service link. Deliberately RELATIVE — /portal/employee/{token} — so the client composes the host it is already on, which is the better pattern where it applies.",
                "operationId": "postApiPayrollEmployeesByIdPortalLink",
                "description": "Requires the `payroll:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "payroll:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "delete": {
                "tags": [
                    "Payroll"
                ],
                "summary": "Revoke the employee's link.",
                "operationId": "deleteApiPayrollEmployeesByIdPortalLink",
                "description": "Requires the `payroll:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "payroll:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/payroll/periods/{periodId}/payment-file": {
            "get": {
                "tags": [
                    "Payroll"
                ],
                "summary": "The salary payment file for an approved run. format=json is the review — rows with the account number masked, the total, and every employee who cannot be paid with the reason. format=csv is the file itself and answers 422 with the reasons when anything blocks, because a file that silently omits the three employees whose banking details were never captured pays seven of ten people and still foots. Not an ACB/BankServ fixed-width file: that layout needs a bank-assigned user code and nominated account Ledgr holds no field for.",
                "operationId": "getApiPayrollPeriodsByPeriodIdPaymentFile",
                "description": "Requires the `payroll:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "payroll:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "periodId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/payroll/returns/ui19": {
            "get": {
                "tags": [
                    "Payroll"
                ],
                "summary": "The UI-19 monthly declaration of employees to the UIF (uFiling). Carries the one thing the EMP201 does not — why each person stopped working here. A leaver with no reason captured, or one whose typed reason does not match a UI-19 reason, BLOCKS the return rather than being guessed at: a benefit claim is assessed against the declared reason, and the wrong one changes what the person may claim.",
                "operationId": "getApiPayrollReturnsUi19",
                "description": "Requires the `payroll:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "payroll:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/payroll/returns/roe": {
            "get": {
                "tags": [
                    "Payroll"
                ],
                "summary": "The COIDA Return of Earnings (W.As.8) for 1 March to end February. Earnings are capped PER EMPLOYEE, which is the whole arithmetic — capping the payroll total instead overstates the assessment for every employer with anybody above the ceiling. Ledgr does not invent the maximum: a year it has no compiled figure for is refused with instructions, or the employer supplies it as ?maxEarnings=.",
                "operationId": "getApiPayrollReturnsRoe",
                "description": "Requires the `payroll:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "payroll:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/payroll/leave-liability": {
            "get": {
                "tags": [
                    "Leave"
                ],
                "summary": "What is owed for annual leave earned and not taken, and whether 2160 agrees.",
                "operationId": "getApiPayrollLeaveLiability",
                "description": "Requires the `payroll:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "payroll:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/leave-requests": {
            "get": {
                "tags": [
                    "Leave"
                ],
                "summary": "Leave requests.",
                "operationId": "getApiLeaveRequests",
                "description": "Requires the `payroll:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "payroll:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Leave"
                ],
                "summary": "Raise a leave request.",
                "operationId": "postApiLeaveRequests",
                "description": "Requires the `payroll:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "payroll:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "employeeId": {
                                        "type": "string"
                                    },
                                    "leaveType": {
                                        "type": "string"
                                    },
                                    "startDate": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "endDate": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "daysRequested": {
                                        "type": "number"
                                    },
                                    "reason": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "employeeId": "<uuid>",
                                "leaveType": "annual",
                                "startDate": "2026-09-14",
                                "endDate": "2026-09-18",
                                "daysRequested": 5.0,
                                "reason": "Family holiday"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/leave-requests/{id}": {
            "get": {
                "tags": [
                    "Leave"
                ],
                "summary": "One request.",
                "operationId": "getApiLeaveRequestsById",
                "description": "Requires the `payroll:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "payroll:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "put": {
                "tags": [
                    "Leave"
                ],
                "summary": "Amend a pending request.",
                "operationId": "putApiLeaveRequestsById",
                "description": "Requires the `payroll:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "payroll:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "employeeId": {
                                        "type": "string"
                                    },
                                    "leaveType": {
                                        "type": "string"
                                    },
                                    "startDate": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "endDate": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "daysRequested": {
                                        "type": "number"
                                    }
                                }
                            },
                            "example": {
                                "employeeId": "<uuid>",
                                "leaveType": "annual",
                                "startDate": "2026-09-14",
                                "endDate": "2026-09-17",
                                "daysRequested": 4.0
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "delete": {
                "tags": [
                    "Leave"
                ],
                "summary": "Withdraw a request.",
                "operationId": "deleteApiLeaveRequestsById",
                "description": "Requires the `payroll:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "payroll:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/leave-requests/{id}/approve": {
            "post": {
                "tags": [
                    "Leave"
                ],
                "summary": "Approve leave and draw down the balance.",
                "operationId": "postApiLeaveRequestsByIdApprove",
                "description": "Requires the `payroll:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "payroll:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/leave-requests/{id}/reject": {
            "post": {
                "tags": [
                    "Leave"
                ],
                "summary": "Decline leave, with a reason.",
                "operationId": "postApiLeaveRequestsByIdReject",
                "description": "Requires the `payroll:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "payroll:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "reason": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "reason": "Two people already off that week"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/portal/employee/{token}": {
            "get": {
                "tags": [
                    "Employee self-service portal"
                ],
                "summary": "The employee's own view — payslips, certificates, leave and their details.",
                "operationId": "getPortalEmployeeByToken",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "token",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/portal/employee/{token}/payslips/{payslipId}": {
            "get": {
                "tags": [
                    "Employee self-service portal"
                ],
                "summary": "One payslip.",
                "operationId": "getPortalEmployeeByTokenPayslipsByPayslipId",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "token",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "payslipId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/portal/employee/{token}/payslips/{payslipId}/pdf": {
            "get": {
                "tags": [
                    "Employee self-service portal"
                ],
                "summary": "That payslip as a PDF.",
                "operationId": "getPortalEmployeeByTokenPayslipsByPayslipIdPdf",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "token",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "payslipId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/portal/employee/{token}/certificates": {
            "get": {
                "tags": [
                    "Employee self-service portal"
                ],
                "summary": "The employee's IRP5/IT3(a) certificates.",
                "operationId": "getPortalEmployeeByTokenCertificates",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "token",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/portal/employee/{token}/certificates/{taxYear}/pdf": {
            "get": {
                "tags": [
                    "Employee self-service portal"
                ],
                "summary": "One certificate as a PDF.",
                "operationId": "getPortalEmployeeByTokenCertificatesByTaxYearPdf",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "token",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "taxYear",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/portal/employee/{token}/leave": {
            "post": {
                "tags": [
                    "Employee self-service portal"
                ],
                "summary": "Request leave, as JSON.",
                "operationId": "postPortalEmployeeByTokenLeave",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "token",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "type": {
                                        "type": "string"
                                    },
                                    "fromDate": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "toDate": {
                                        "type": "string",
                                        "format": "date"
                                    }
                                }
                            },
                            "example": {
                                "type": "annual",
                                "fromDate": "2026-12-16",
                                "toDate": "2026-12-24"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/portal/employee/{token}/forms/leave": {
            "post": {
                "tags": [
                    "Employee self-service portal"
                ],
                "summary": "The same request as a plain form post. The page sends Referrer-Policy: no-referrer to keep its own token out of a third party's logs, which is why these posts sit outside the CORS gate.",
                "operationId": "postPortalEmployeeByTokenFormsLeave",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "token",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/portal/employee/{token}/change-request": {
            "post": {
                "tags": [
                    "Employee self-service portal"
                ],
                "summary": "Ask for a detail change — address, bank account, contact number — as JSON. It is a request, not a write.",
                "operationId": "postPortalEmployeeByTokenChangeRequest",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "token",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "field": {
                                        "type": "string"
                                    },
                                    "newValue": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "field": "bankAccountNumber",
                                "newValue": "62811111111"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/portal/employee/{token}/forms/change": {
            "post": {
                "tags": [
                    "Employee self-service portal"
                ],
                "summary": "The same change request as a form post.",
                "operationId": "postPortalEmployeeByTokenFormsChange",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "token",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/inventory/products": {
            "get": {
                "tags": [
                    "Inventory"
                ],
                "summary": "The catalogue, paged. Search asks the SERVER, on name, SKU and barcode — the barcode arm matters because a typed barcode used to work only while the list was one page.",
                "operationId": "getApiInventoryProducts",
                "description": "Requires the `inventory:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "items": {
                                            "type": "array",
                                            "items": {
                                                "type": "object",
                                                "properties": {
                                                    "id": {
                                                        "type": "string"
                                                    },
                                                    "name": {
                                                        "type": "string"
                                                    },
                                                    "sku": {
                                                        "type": "string"
                                                    },
                                                    "stockQty": {
                                                        "type": "integer"
                                                    },
                                                    "stockValue": {
                                                        "type": "number"
                                                    }
                                                }
                                            }
                                        },
                                        "total": {
                                            "type": "integer"
                                        },
                                        "page": {
                                            "type": "integer"
                                        },
                                        "limit": {
                                            "type": "integer"
                                        },
                                        "totalPages": {
                                            "type": "integer"
                                        }
                                    }
                                },
                                "example": {
                                    "items": [
                                        {
                                            "id": "<uuid>",
                                            "name": "Developer Kit",
                                            "sku": "HW-DEV-K01",
                                            "stockQty": 42,
                                            "stockValue": 336000.0
                                        }
                                    ],
                                    "total": 1340,
                                    "page": 1,
                                    "limit": 50,
                                    "totalPages": 27
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Inventory"
                ],
                "summary": "Create a product. SKU is unique per tenant, case-insensitively, over live rows; a negative price and a VAT rate outside 0–100 are refused.",
                "operationId": "postApiInventoryProducts",
                "description": "Requires the `inventory:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string"
                                    },
                                    "sku": {
                                        "type": "string"
                                    },
                                    "sellPrice": {
                                        "type": "number"
                                    },
                                    "costPrice": {
                                        "type": "number"
                                    },
                                    "unit": {
                                        "type": "string"
                                    },
                                    "vatRate": {
                                        "type": "number"
                                    },
                                    "costingMethod": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "name": "Developer Kit",
                                "sku": "HW-DEV-K01",
                                "sellPrice": 12500.0,
                                "costPrice": 8000.0,
                                "unit": "unit",
                                "vatRate": 15.0,
                                "costingMethod": "FIFO"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/inventory/products/{id}": {
            "get": {
                "tags": [
                    "Inventory"
                ],
                "summary": "One product, with its stock value for the active company.",
                "operationId": "getApiInventoryProductsById",
                "description": "Requires the `inventory:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "put": {
                "tags": [
                    "Inventory"
                ],
                "summary": "Update a product.",
                "operationId": "putApiInventoryProductsById",
                "description": "Requires the `inventory:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "sellPrice": {
                                        "type": "number"
                                    }
                                }
                            },
                            "example": {
                                "sellPrice": 12900.0
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "delete": {
                "tags": [
                    "Inventory"
                ],
                "summary": "Soft-delete a product. REFUSED while there is stock to lose — a write-off has to be an adjustment somebody posts, not a silent disappearance with the value still sitting in 1310.",
                "operationId": "deleteApiInventoryProductsById",
                "description": "Requires the `inventory:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/inventory/products/lookup": {
            "get": {
                "tags": [
                    "Inventory"
                ],
                "summary": "Resolve one product by barcode or SKU, for the scanner and the line picker. Takes the oldest match rather than nothing, for tenants whose data predates the uniqueness guard.",
                "operationId": "getApiInventoryProductsLookup",
                "description": "Requires the `inventory:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/inventory/products/low-stock": {
            "get": {
                "tags": [
                    "Inventory"
                ],
                "summary": "Everything below its reorder point, tenant-wide — counting the loaded page would report page one's shortages as the whole catalogue.",
                "operationId": "getApiInventoryProductsLowStock",
                "description": "Requires the `inventory:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/inventory/products/low-stock-by-location": {
            "get": {
                "tags": [
                    "Inventory"
                ],
                "summary": "The same, split by location.",
                "operationId": "getApiInventoryProductsLowStockByLocation",
                "description": "Requires the `inventory:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/inventory/products/{id}/images": {
            "get": {
                "tags": [
                    "Inventory"
                ],
                "summary": "Product photographs.",
                "operationId": "getApiInventoryProductsByIdImages",
                "description": "Requires the `inventory:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Inventory"
                ],
                "summary": "Upload a photograph. Raw bytes; the server recompresses and stores the object, never base64 in Postgres.",
                "operationId": "postApiInventoryProductsByIdImages",
                "description": "Requires the `inventory:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/inventory/products/{id}/images/{imageId}": {
            "delete": {
                "tags": [
                    "Inventory"
                ],
                "summary": "Remove a photograph.",
                "operationId": "deleteApiInventoryProductsByIdImagesByImageId",
                "description": "Requires the `inventory:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    },
                    {
                        "name": "imageId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/inventory/categories": {
            "get": {
                "tags": [
                    "Inventory"
                ],
                "summary": "Product categories.",
                "operationId": "getApiInventoryCategories",
                "description": "Requires the `inventory:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Inventory"
                ],
                "summary": "Add a category.",
                "operationId": "postApiInventoryCategories",
                "description": "Requires the `inventory:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "code": {
                                        "type": "string"
                                    },
                                    "name": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "code": "SKU-1001",
                                "name": "Example"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/inventory/categories/{id}": {
            "put": {
                "tags": [
                    "Inventory"
                ],
                "summary": "Rename a category.",
                "operationId": "putApiInventoryCategoriesById",
                "description": "Requires the `inventory:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "name": "Workshop consumables"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "delete": {
                "tags": [
                    "Inventory"
                ],
                "summary": "Remove a category.",
                "operationId": "deleteApiInventoryCategoriesById",
                "description": "Requires the `inventory:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/inventory/variants": {
            "get": {
                "tags": [
                    "Inventory"
                ],
                "summary": "Product variants — size, colour, configuration.",
                "operationId": "getApiInventoryVariants",
                "description": "Requires the `inventory:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Inventory"
                ],
                "summary": "Add a variant.",
                "operationId": "postApiInventoryVariants",
                "description": "Requires the `inventory:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "productId": {
                                        "type": "string"
                                    },
                                    "name": {
                                        "type": "string"
                                    },
                                    "sku": {
                                        "type": "string"
                                    },
                                    "sellPrice": {
                                        "type": "number"
                                    }
                                }
                            },
                            "example": {
                                "productId": "<uuid>",
                                "name": "16GB / 512GB",
                                "sku": "HW-DEV-K01-16-512",
                                "sellPrice": 14500.0
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/inventory/variants/{id}": {
            "put": {
                "tags": [
                    "Inventory"
                ],
                "summary": "Update a variant.",
                "operationId": "putApiInventoryVariantsById",
                "description": "Requires the `inventory:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "sellPrice": {
                                        "type": "number"
                                    }
                                }
                            },
                            "example": {
                                "sellPrice": 14900.0
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "delete": {
                "tags": [
                    "Inventory"
                ],
                "summary": "Remove a variant.",
                "operationId": "deleteApiInventoryVariantsById",
                "description": "Requires the `inventory:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/inventory/locations": {
            "get": {
                "tags": [
                    "Inventory"
                ],
                "summary": "Stock locations.",
                "operationId": "getApiInventoryLocations",
                "description": "Requires the `inventory:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Inventory"
                ],
                "summary": "Add a location.",
                "operationId": "postApiInventoryLocations",
                "description": "Requires the `inventory:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "name": "Durban Showroom"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/inventory/locations/{id}": {
            "put": {
                "tags": [
                    "Inventory"
                ],
                "summary": "Rename a location.",
                "operationId": "putApiInventoryLocationsById",
                "description": "Requires the `inventory:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "name": "Durban Showroom (Umhlanga)"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "delete": {
                "tags": [
                    "Inventory"
                ],
                "summary": "Remove a location. REFUSED while it holds stock — its stock_levels rows would otherwise point at a warehouse no list shows, while still counting towards the product's total.",
                "operationId": "deleteApiInventoryLocationsById",
                "description": "Requires the `inventory:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/inventory/stock-levels": {
            "get": {
                "tags": [
                    "Inventory"
                ],
                "summary": "On-hand by product and location.",
                "operationId": "getApiInventoryStockLevels",
                "description": "Requires the `inventory:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/inventory/movements": {
            "get": {
                "tags": [
                    "Inventory"
                ],
                "summary": "Stock movement history with a running balance.",
                "operationId": "getApiInventoryMovements",
                "description": "Requires the `inventory:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/inventory/adjust": {
            "post": {
                "tags": [
                    "Inventory"
                ],
                "summary": "Adjust stock up or down and post the journal. A negative resulting level is refused.",
                "operationId": "postApiInventoryAdjust",
                "description": "Requires the `inventory:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "productId": {
                                        "type": "string"
                                    },
                                    "deltaQty": {
                                        "type": "number"
                                    },
                                    "reason": {
                                        "type": "string"
                                    },
                                    "locationId": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "productId": "<uuid>",
                                "deltaQty": 2.0,
                                "reason": "Captured during the August review",
                                "locationId": "<uuid>"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/inventory/reorder-points": {
            "get": {
                "tags": [
                    "Inventory"
                ],
                "summary": "Reorder points and reorder quantities.",
                "operationId": "getApiInventoryReorderPoints",
                "description": "Requires the `inventory:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "put": {
                "tags": [
                    "Inventory"
                ],
                "summary": "Set a reorder point.",
                "operationId": "putApiInventoryReorderPoints",
                "description": "Requires the `inventory:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "productId": {
                                        "type": "string"
                                    },
                                    "locationId": {
                                        "type": "string"
                                    },
                                    "reorderPoint": {
                                        "type": "number"
                                    },
                                    "reorderQty": {
                                        "type": "number"
                                    }
                                }
                            },
                            "example": {
                                "productId": "<uuid>",
                                "locationId": "<uuid>",
                                "reorderPoint": 20.0,
                                "reorderQty": 100.0
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/inventory/transfers": {
            "get": {
                "tags": [
                    "Inventory"
                ],
                "summary": "Inter-location transfers.",
                "operationId": "getApiInventoryTransfers",
                "description": "Requires the `inventory:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/inventory/transfer": {
            "post": {
                "tags": [
                    "Inventory"
                ],
                "summary": "Raise a transfer. A shortfall is refused rather than driving the source negative, and the batch layers travel with the goods.",
                "operationId": "postApiInventoryTransfer",
                "description": "Requires the `inventory:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "productId": {
                                        "type": "string"
                                    },
                                    "fromLocationId": {
                                        "type": "string"
                                    },
                                    "toLocationId": {
                                        "type": "string"
                                    },
                                    "qty": {
                                        "type": "number"
                                    }
                                }
                            },
                            "example": {
                                "productId": "<uuid>",
                                "fromLocationId": "<uuid>",
                                "toLocationId": "<uuid>",
                                "qty": 2.0
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/inventory/transfers/{id}/confirm": {
            "post": {
                "tags": [
                    "Inventory"
                ],
                "summary": "Receive a transfer. StockCostingEngine.relocate moves the batch layers HERE — on receipt, not on despatch, because the sum of batch values has to keep equalling 1310 at every instant.",
                "operationId": "postApiInventoryTransfersByIdConfirm",
                "description": "Requires the `inventory:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "notes": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "notes": "Received in full, no damage"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/inventory/transfers/{id}/cancel": {
            "post": {
                "tags": [
                    "Inventory"
                ],
                "summary": "Cancel a transfer in flight.",
                "operationId": "postApiInventoryTransfersByIdCancel",
                "description": "Requires the `inventory:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/inventory/batches": {
            "get": {
                "tags": [
                    "Inventory"
                ],
                "summary": "Batch layers — quantity remaining, unit cost, supplier, received and expiry dates, and the location they are actually at.",
                "operationId": "getApiInventoryBatches",
                "description": "Requires the `inventory:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Inventory"
                ],
                "summary": "Create a batch layer.",
                "operationId": "postApiInventoryBatches",
                "description": "Requires the `inventory:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "productId": {
                                        "type": "string"
                                    },
                                    "batchNumber": {
                                        "type": "string"
                                    },
                                    "receivedDate": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "qtyReceived": {
                                        "type": "number"
                                    },
                                    "unitCost": {
                                        "type": "number"
                                    },
                                    "locationId": {
                                        "type": "string"
                                    },
                                    "expiryDate": {
                                        "type": "string",
                                        "format": "date"
                                    }
                                }
                            },
                            "example": {
                                "productId": "<uuid>",
                                "batchNumber": "Example",
                                "receivedDate": "2026-09-30",
                                "qtyReceived": 2.0,
                                "unitCost": 1500.0,
                                "locationId": "<uuid>",
                                "expiryDate": "2027-08-20"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/inventory/batches/expiring": {
            "get": {
                "tags": [
                    "Inventory"
                ],
                "summary": "Batches expiring inside a window, with the location that holds them — naming the wrong branch is worse than naming none.",
                "operationId": "getApiInventoryBatchesExpiring",
                "description": "Requires the `inventory:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/inventory/batches/{id}": {
            "delete": {
                "tags": [
                    "Inventory"
                ],
                "summary": "Remove an empty batch layer.",
                "operationId": "deleteApiInventoryBatchesById",
                "description": "Requires the `inventory:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/inventory/serials": {
            "get": {
                "tags": [
                    "Inventory"
                ],
                "summary": "Serialised units and their status.",
                "operationId": "getApiInventorySerials",
                "description": "Requires the `inventory:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Inventory"
                ],
                "summary": "Register serial numbers against a batch.",
                "operationId": "postApiInventorySerials",
                "description": "Requires the `inventory:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "productId": {
                                        "type": "string"
                                    },
                                    "batchId": {
                                        "type": "string"
                                    },
                                    "serials": {
                                        "type": "array",
                                        "items": {
                                            "type": "string"
                                        }
                                    }
                                }
                            },
                            "example": {
                                "productId": "<uuid>",
                                "batchId": "<uuid>",
                                "serials": [
                                    "SN-0001",
                                    "SN-0002"
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/inventory/serials/{id}": {
            "get": {
                "tags": [
                    "Inventory"
                ],
                "summary": "One serialised unit and its history.",
                "operationId": "getApiInventorySerialsById",
                "description": "Requires the `inventory:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "delete": {
                "tags": [
                    "Inventory"
                ],
                "summary": "Remove a serial registered in error.",
                "operationId": "deleteApiInventorySerialsById",
                "description": "Requires the `inventory:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/inventory/serials/lookup": {
            "get": {
                "tags": [
                    "Inventory"
                ],
                "summary": "Find a unit by its serial.",
                "operationId": "getApiInventorySerialsLookup",
                "description": "Requires the `inventory:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/inventory/serials/summary": {
            "get": {
                "tags": [
                    "Inventory"
                ],
                "summary": "Counts by status — in stock, sold, returned, scrapped.",
                "operationId": "getApiInventorySerialsSummary",
                "description": "Requires the `inventory:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/inventory/serials/assign": {
            "post": {
                "tags": [
                    "Inventory"
                ],
                "summary": "Assign serials to a document line at despatch.",
                "operationId": "postApiInventorySerialsAssign",
                "description": "Requires the `inventory:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "invoiceId": {
                                        "type": "string"
                                    },
                                    "serialIds": {
                                        "type": "array",
                                        "items": {
                                            "type": "string"
                                        }
                                    },
                                    "productId": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "invoiceId": "<uuid>",
                                "serialIds": [
                                    "<uuid>"
                                ],
                                "productId": "<uuid>"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/inventory/serials/{id}/scrap": {
            "post": {
                "tags": [
                    "Inventory"
                ],
                "summary": "Write a unit off. Drains its OWN batch by one at that batch's cost — specific identification, which is the point of a serial — and posts DR 5070 / CR 1310. Only from in_stock.",
                "operationId": "postApiInventorySerialsByIdScrap",
                "description": "Requires the `inventory:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "notes": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "notes": "Water damage"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/inventory/serials/{id}/return": {
            "post": {
                "tags": [
                    "Inventory"
                ],
                "summary": "Return a unit to stock. A returned unit is back on the shelf only if a credit note restocked it.",
                "operationId": "postApiInventorySerialsByIdReturn",
                "description": "Requires the `inventory:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "notes": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "notes": "Customer return, resold as open-box"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/inventory/stock-takes": {
            "get": {
                "tags": [
                    "Inventory"
                ],
                "summary": "Stock takes and their variance.",
                "operationId": "getApiInventoryStockTakes",
                "description": "Requires the `inventory:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Inventory"
                ],
                "summary": "Open a stock take.",
                "operationId": "postApiInventoryStockTakes",
                "description": "Requires the `inventory:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string"
                                    },
                                    "locationId": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "name": "Example",
                                "locationId": "<uuid>"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/inventory/stock-takes/{id}": {
            "get": {
                "tags": [
                    "Inventory"
                ],
                "summary": "One stock take with its counted lines.",
                "operationId": "getApiInventoryStockTakesById",
                "description": "Requires the `inventory:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/inventory/stock-takes/{id}/lines": {
            "put": {
                "tags": [
                    "Inventory"
                ],
                "summary": "Save counts. The variance is written WHEN COUNTS ARE SAVED, not only at commit — the list used to tell a supervisor a count with eight units missing was clean.",
                "operationId": "putApiInventoryStockTakesByIdLines",
                "description": "Requires the `inventory:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "counts": {
                                        "type": "array",
                                        "items": {
                                            "type": "object",
                                            "properties": {
                                                "lineId": {
                                                    "type": "string"
                                                },
                                                "countedQty": {
                                                    "type": "number"
                                                }
                                            }
                                        }
                                    }
                                }
                            },
                            "example": {
                                "counts": [
                                    {
                                        "lineId": "<uuid>",
                                        "countedQty": 2.0
                                    }
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/inventory/stock-takes/{id}/commit": {
            "post": {
                "tags": [
                    "Inventory"
                ],
                "summary": "Post the variance as adjustments.",
                "operationId": "postApiInventoryStockTakesByIdCommit",
                "description": "Requires the `inventory:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/inventory/stock-takes/{id}/cancel": {
            "post": {
                "tags": [
                    "Inventory"
                ],
                "summary": "Abandon a stock take.",
                "operationId": "postApiInventoryStockTakesByIdCancel",
                "description": "Requires the `inventory:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/inventory/boms": {
            "get": {
                "tags": [
                    "Inventory"
                ],
                "summary": "Assembly recipes. These are V96's simple recipes at the Business tier; the deep BOM work is /api/bom on the Manufacturing plan.",
                "operationId": "getApiInventoryBoms",
                "description": "Requires the `inventory:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Inventory"
                ],
                "summary": "Create a recipe.",
                "operationId": "postApiInventoryBoms",
                "description": "Requires the `inventory:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "productId": {
                                        "type": "string"
                                    },
                                    "name": {
                                        "type": "string"
                                    },
                                    "lines": {
                                        "type": "array",
                                        "items": {
                                            "type": "object",
                                            "properties": {
                                                "componentProductId": {
                                                    "type": "string"
                                                },
                                                "quantity": {
                                                    "type": "number"
                                                }
                                            }
                                        }
                                    }
                                }
                            },
                            "example": {
                                "productId": "<uuid>",
                                "name": "Example",
                                "lines": [
                                    {
                                        "componentProductId": "<uuid>",
                                        "quantity": 4.0
                                    }
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/inventory/boms/{id}": {
            "get": {
                "tags": [
                    "Inventory"
                ],
                "summary": "One recipe.",
                "operationId": "getApiInventoryBomsById",
                "description": "Requires the `inventory:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "put": {
                "tags": [
                    "Inventory"
                ],
                "summary": "Change a recipe.",
                "operationId": "putApiInventoryBomsById",
                "description": "Requires the `inventory:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "productId": {
                                        "type": "string"
                                    },
                                    "name": {
                                        "type": "string"
                                    },
                                    "lines": {
                                        "type": "array",
                                        "items": {
                                            "type": "object",
                                            "properties": {
                                                "componentProductId": {
                                                    "type": "string"
                                                },
                                                "quantity": {
                                                    "type": "number"
                                                }
                                            }
                                        }
                                    }
                                }
                            },
                            "example": {
                                "productId": "<uuid>",
                                "name": "Example",
                                "lines": [
                                    {
                                        "componentProductId": "<uuid>",
                                        "quantity": 5.0
                                    }
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "delete": {
                "tags": [
                    "Inventory"
                ],
                "summary": "Delete a recipe.",
                "operationId": "deleteApiInventoryBomsById",
                "description": "Requires the `inventory:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/inventory/boms/{id}/cost": {
            "get": {
                "tags": [
                    "Inventory"
                ],
                "summary": "What the recipe costs at current batch costs.",
                "operationId": "getApiInventoryBomsByIdCost",
                "description": "Requires the `inventory:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/inventory/boms/using/{productId}": {
            "get": {
                "tags": [
                    "Inventory"
                ],
                "summary": "Which recipes use this component.",
                "operationId": "getApiInventoryBomsUsingByProductId",
                "description": "Requires the `inventory:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "productId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/inventory/builds": {
            "get": {
                "tags": [
                    "Inventory"
                ],
                "summary": "Assembly builds.",
                "operationId": "getApiInventoryBuilds",
                "description": "Requires the `inventory:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Inventory"
                ],
                "summary": "Build assemblies from components. A short build is REFUSED, naming what is short and what maxBuildable says can be built now — costing a shortfall at list price once put the sum of batch values R61 441 above 1310 with no entry that could close it.",
                "operationId": "postApiInventoryBuilds",
                "description": "Requires the `inventory:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "bomId": {
                                        "type": "string"
                                    },
                                    "qtyToBuild": {
                                        "type": "number"
                                    },
                                    "locationId": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "bomId": "<uuid>",
                                "qtyToBuild": 2.0,
                                "locationId": "<uuid>"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/inventory/builds/{id}": {
            "get": {
                "tags": [
                    "Inventory"
                ],
                "summary": "One build and what it consumed.",
                "operationId": "getApiInventoryBuildsById",
                "description": "Requires the `inventory:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/inventory/builds/{id}/cancel": {
            "post": {
                "tags": [
                    "Inventory"
                ],
                "summary": "Reverse a build, returning components to their layers.",
                "operationId": "postApiInventoryBuildsByIdCancel",
                "description": "Requires the `inventory:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "inventory:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/job-cards": {
            "get": {
                "tags": [
                    "Job cards"
                ],
                "summary": "Job cards, server-paged and server-filtered. It used to fetch one page of 100 and filter client-side, so a workshop lost its history after a fortnight.",
                "operationId": "getApiJobCards",
                "description": "Requires the `job-cards:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "job-cards:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Job cards"
                ],
                "summary": "Raise a job. Accepts a client-minted id, which is what makes offline capture replayable — the CARD queues, but issuing parts and converting stay online-only.",
                "operationId": "postApiJobCards",
                "description": "Requires the `job-cards:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "job-cards:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "contactId": {
                                        "type": "string"
                                    },
                                    "title": {
                                        "type": "string"
                                    },
                                    "workRequested": {
                                        "type": "string"
                                    },
                                    "sourceQuoteId": {
                                        "type": "null"
                                    }
                                }
                            },
                            "example": {
                                "contactId": "<uuid>",
                                "title": "Example",
                                "workRequested": "Replace element and thermostat",
                                "sourceQuoteId": null
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/job-cards/{id}": {
            "get": {
                "tags": [
                    "Job cards"
                ],
                "summary": "One job with its labour, parts, costs and sign-off.",
                "operationId": "getApiJobCardsById",
                "description": "Requires the `job-cards:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "job-cards:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "put": {
                "tags": [
                    "Job cards"
                ],
                "summary": "Update a job.",
                "operationId": "putApiJobCardsById",
                "description": "Requires the `job-cards:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "job-cards:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "workRequested": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "workRequested": "Replace element, thermostat and drip tray"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "delete": {
                "tags": [
                    "Job cards"
                ],
                "summary": "Delete a job.",
                "operationId": "deleteApiJobCardsById",
                "description": "Requires the `job-cards:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "job-cards:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/job-cards/{id}/status": {
            "post": {
                "tags": [
                    "Job cards"
                ],
                "summary": "Move a job through open, in progress, awaiting parts, ready to invoice, closed.",
                "operationId": "postApiJobCardsByIdStatus",
                "description": "Requires the `job-cards:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "job-cards:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "status": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "status": "awaiting_parts"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/job-cards/summary": {
            "get": {
                "tags": [
                    "Job cards"
                ],
                "summary": "The board's tiles. Windowed to twelve months plus every open job.",
                "operationId": "getApiJobCardsSummary",
                "description": "Requires the `job-cards:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "job-cards:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/job-cards/{id}/labour": {
            "post": {
                "tags": [
                    "Job cards"
                ],
                "summary": "Book labour. The rates default from users.default_charge_rate and default_cost_rate — before those existed, cost_rate defaulted to zero and every job reported 100% margin.",
                "operationId": "postApiJobCardsByIdLabour",
                "description": "Requires the `job-cards:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "job-cards:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "workDate": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "hours": {
                                        "type": "number"
                                    },
                                    "userId": {
                                        "type": "string"
                                    },
                                    "chargeRate": {
                                        "type": "number"
                                    },
                                    "costRate": {
                                        "type": "number"
                                    }
                                }
                            },
                            "example": {
                                "workDate": "2026-09-30",
                                "hours": 2.0,
                                "userId": "<uuid>",
                                "chargeRate": 650.0,
                                "costRate": 320.0
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/job-cards/{id}/labour/{lineId}": {
            "put": {
                "tags": [
                    "Job cards"
                ],
                "summary": "Correct a labour line.",
                "operationId": "putApiJobCardsByIdLabourByLineId",
                "description": "Requires the `job-cards:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "job-cards:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    },
                    {
                        "name": "lineId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "hours": {
                                        "type": "number"
                                    }
                                }
                            },
                            "example": {
                                "hours": 4.0
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "delete": {
                "tags": [
                    "Job cards"
                ],
                "summary": "Remove a labour line.",
                "operationId": "deleteApiJobCardsByIdLabourByLineId",
                "description": "Requires the `job-cards:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "job-cards:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    },
                    {
                        "name": "lineId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/job-cards/{id}/labour/import-time": {
            "post": {
                "tags": [
                    "Job cards"
                ],
                "summary": "Pull hours in from time tracking. Imports only APPROVED entries — the project invoicer always required it and this path did not, so approvals were bypassed by whichever of the two billing paths somebody used.",
                "operationId": "postApiJobCardsByIdLabourImportTime",
                "description": "Requires the `job-cards:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "job-cards:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "timeEntryIds": {
                                        "type": "array",
                                        "items": {
                                            "type": "string"
                                        }
                                    }
                                }
                            },
                            "example": {
                                "timeEntryIds": [
                                    "<uuid>"
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/job-cards/{id}/parts": {
            "post": {
                "tags": [
                    "Job cards"
                ],
                "summary": "Add a part to the job, before it is issued.",
                "operationId": "postApiJobCardsByIdParts",
                "description": "Requires the `job-cards:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "job-cards:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "description": {
                                        "type": "string"
                                    },
                                    "qty": {
                                        "type": "number"
                                    },
                                    "productId": {
                                        "type": "string"
                                    },
                                    "billable": {
                                        "type": "boolean"
                                    }
                                }
                            },
                            "example": {
                                "description": "Captured during the August review",
                                "qty": 2.0,
                                "productId": "<uuid>",
                                "billable": true
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/job-cards/{id}/parts/{lineId}": {
            "put": {
                "tags": [
                    "Job cards"
                ],
                "summary": "Change a part line.",
                "operationId": "putApiJobCardsByIdPartsByLineId",
                "description": "Requires the `job-cards:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "job-cards:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    },
                    {
                        "name": "lineId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "qty": {
                                        "type": "number"
                                    }
                                }
                            },
                            "example": {
                                "qty": 2.0
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "delete": {
                "tags": [
                    "Job cards"
                ],
                "summary": "Remove a part line.",
                "operationId": "deleteApiJobCardsByIdPartsByLineId",
                "description": "Requires the `job-cards:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "job-cards:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    },
                    {
                        "name": "lineId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/job-cards/{id}/parts/{lineId}/issue": {
            "post": {
                "tags": [
                    "Job cards"
                ],
                "summary": "Issue the part from stock at batch cost. What is in the van is out of the storeroom in the figures as well as in fact.",
                "operationId": "postApiJobCardsByIdPartsByLineIdIssue",
                "description": "Requires the `job-cards:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "job-cards:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    },
                    {
                        "name": "lineId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "locationId": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "locationId": "<uuid>"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/job-cards/{id}/parts/{lineId}/return": {
            "post": {
                "tags": [
                    "Job cards"
                ],
                "summary": "Return an unused part to stock.",
                "operationId": "postApiJobCardsByIdPartsByLineIdReturn",
                "description": "Requires the `job-cards:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "job-cards:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    },
                    {
                        "name": "lineId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/job-cards/{id}/costs": {
            "post": {
                "tags": [
                    "Job cards"
                ],
                "summary": "Add an on-cost typed by hand — a subcontractor, a callout fee.",
                "operationId": "postApiJobCardsByIdCosts",
                "description": "Requires the `job-cards:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "job-cards:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "description": {
                                        "type": "string"
                                    },
                                    "amount": {
                                        "type": "number"
                                    },
                                    "markupPercent": {
                                        "type": "number"
                                    },
                                    "billable": {
                                        "type": "boolean"
                                    }
                                }
                            },
                            "example": {
                                "description": "Crane hire",
                                "amount": 4200.0,
                                "markupPercent": 10.0,
                                "billable": true
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/job-cards/{id}/costs/allocate": {
            "post": {
                "tags": [
                    "Job cards"
                ],
                "summary": "Allocate a real expense or bill to the job. JobCostAllocation is the single writer of both halves — the column on the purchase and the mirroring cost line — because either alone is a defect with a plausible screen.",
                "operationId": "postApiJobCardsByIdCostsAllocate",
                "description": "Requires the `job-cards:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "job-cards:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "expenseIds": {
                                        "type": "array",
                                        "items": {
                                            "type": "string"
                                        }
                                    },
                                    "markupPercent": {
                                        "type": "number"
                                    },
                                    "billable": {
                                        "type": "boolean"
                                    }
                                }
                            },
                            "example": {
                                "expenseIds": [
                                    "<uuid>"
                                ],
                                "markupPercent": 15.0,
                                "billable": true
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/job-cards/{id}/costs/{lineId}": {
            "put": {
                "tags": [
                    "Job cards"
                ],
                "summary": "Change a cost line. An allocated purchase's AMOUNT is refused here and the response names where to correct it — what a job cost is a fact about the purchase; what the customer is charged is a decision about the job, so markup and billable stay editable.",
                "operationId": "putApiJobCardsByIdCostsByLineId",
                "description": "Requires the `job-cards:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "job-cards:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    },
                    {
                        "name": "lineId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "markupPercent": {
                                        "type": "number"
                                    }
                                }
                            },
                            "example": {
                                "markupPercent": 20.0
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "delete": {
                "tags": [
                    "Job cards"
                ],
                "summary": "Remove a cost line.",
                "operationId": "deleteApiJobCardsByIdCostsByLineId",
                "description": "Requires the `job-cards:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "job-cards:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    },
                    {
                        "name": "lineId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/job-cards/allocatable-costs": {
            "get": {
                "tags": [
                    "Job cards"
                ],
                "summary": "Expenses and bills that could still be allocated to a job.",
                "operationId": "getApiJobCardsAllocatableCosts",
                "description": "Requires the `job-cards:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "job-cards:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/job-cards/technicians": {
            "get": {
                "tags": [
                    "Job cards"
                ],
                "summary": "Who can be assigned, with their default rates.",
                "operationId": "getApiJobCardsTechnicians",
                "description": "Requires the `job-cards:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "job-cards:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/job-cards/technicians/{userId}/rates": {
            "put": {
                "tags": [
                    "Job cards"
                ],
                "summary": "Set a person's default charge and cost rates. Deliberately NOT a per-project-per-person rate table — users owns the figure for both modules.",
                "operationId": "putApiJobCardsTechniciansByUserIdRates",
                "description": "Requires the `job-cards:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "job-cards:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "userId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "defaultChargeRate": {
                                        "type": "number"
                                    },
                                    "defaultCostRate": {
                                        "type": "number"
                                    }
                                }
                            },
                            "example": {
                                "defaultChargeRate": 650.0,
                                "defaultCostRate": 320.0
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/job-cards/{id}/technicians": {
            "post": {
                "tags": [
                    "Job cards"
                ],
                "summary": "Assign a technician to the job.",
                "operationId": "postApiJobCardsByIdTechnicians",
                "description": "Requires the `job-cards:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "job-cards:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "userId": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "userId": "<uuid>"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/job-cards/{id}/technicians/{userId}": {
            "delete": {
                "tags": [
                    "Job cards"
                ],
                "summary": "Unassign a technician.",
                "operationId": "deleteApiJobCardsByIdTechniciansByUserId",
                "description": "Requires the `job-cards:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "job-cards:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    },
                    {
                        "name": "userId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/job-cards/{id}/sign-off": {
            "post": {
                "tags": [
                    "Job cards"
                ],
                "summary": "Capture the customer's signature. The strokes become a 1-bit PNG in object storage as an AttachmentKind.SIGNATURE — never base64 into the column, which tenantStorageBytes does not count.",
                "operationId": "postApiJobCardsByIdSignOff",
                "description": "Requires the `job-cards:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "job-cards:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "signOffName": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "signOffName": "Example"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/job-cards/{id}/convert": {
            "post": {
                "tags": [
                    "Job cards"
                ],
                "summary": "Raise the invoice. Charges the VAT the tenant may actually charge — a company with no vat_number is not registered, and part lines bill at their own product's rate rather than a flat 15%.",
                "operationId": "postApiJobCardsByIdConvert",
                "description": "Requires the `job-cards:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "job-cards:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "issueDate": {
                                        "type": "string",
                                        "format": "date"
                                    }
                                }
                            },
                            "example": {
                                "issueDate": "2026-08-26"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/job-cards/{id}/report": {
            "get": {
                "tags": [
                    "Job cards"
                ],
                "summary": "The customer's job report — what was done, what was used, and the signature.",
                "operationId": "getApiJobCardsByIdReport",
                "description": "Requires the `job-cards:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "job-cards:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/job-cards/{id}/report/send": {
            "post": {
                "tags": [
                    "Job cards"
                ],
                "summary": "Email the customer their signed job report, with the PDF attached.",
                "operationId": "postApiJobCardsByIdReportSend",
                "description": "Requires the `job-cards:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "job-cards:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "email": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "email": "accounts@coastalcold.co.za"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/job-cards/dispatch": {
            "get": {
                "tags": [
                    "Job cards"
                ],
                "summary": "The dispatcher's day or week - a lane per technician, work placed by the clock, clashes flagged. Layout is computed by shared/domain/jobcards/DispatchBoard.kt so the screen cannot paint an overlap the server would not see. window=day|week, date=ISO anchor; an unknown window is refused rather than widened.",
                "operationId": "getApiJobCardsDispatch",
                "description": "Requires the `job-cards:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "job-cards:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/job-cards/{id}/dispatch": {
            "post": {
                "tags": [
                    "Job cards"
                ],
                "summary": "Reschedule and reassign in ONE write - what a drag across the board is. Two calls would leave a job at a time nobody chose when the second failed. unassignUserId is what makes a drag out of a lane take the job off THAT technician only.",
                "operationId": "postApiJobCardsByIdDispatch",
                "description": "Requires the `job-cards:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "job-cards:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "scheduledStart": {
                                        "type": "string"
                                    },
                                    "scheduledEnd": {
                                        "type": "string"
                                    },
                                    "assignUserId": {
                                        "type": "string"
                                    },
                                    "unassignUserId": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "scheduledStart": "2026-08-27T08:00:00",
                                "scheduledEnd": "2026-08-27T10:00:00",
                                "assignUserId": "00000000-0000-0000-0000-000000000000",
                                "unassignUserId": "00000000-0000-0000-0000-000000000000"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/job-cards/stock-locations": {
            "get": {
                "tags": [
                    "Job cards"
                ],
                "summary": "The stock locations the van picker may offer. Served here on jobcards:view because a role holding only jobcards:* cannot call the inventory tree at all - the same reason /api/bom/products exists.",
                "operationId": "getApiJobCardsStockLocations",
                "description": "Requires the `job-cards:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "job-cards:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/job-cards/reports/wip": {
            "get": {
                "tags": [
                    "Job cards"
                ],
                "summary": "Work in progress — cost incurred and not yet billed. On a bad month it is most of a business's cash.",
                "operationId": "getApiJobCardsReportsWip",
                "description": "Requires the `job-cards:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "job-cards:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/job-cards/reports/profitability": {
            "get": {
                "tags": [
                    "Job cards"
                ],
                "summary": "Margin per job, measured against quotedAmount where the job came from an accepted quote.",
                "operationId": "getApiJobCardsReportsProfitability",
                "description": "Requires the `job-cards:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "job-cards:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/job-cards/reports/by-status": {
            "get": {
                "tags": [
                    "Job cards"
                ],
                "summary": "Jobs by state, for the board's counts.",
                "operationId": "getApiJobCardsReportsByStatus",
                "description": "Requires the `job-cards:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "job-cards:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/job-cards/reports/completion-time": {
            "get": {
                "tags": [
                    "Job cards"
                ],
                "summary": "How long jobs take, from raised to closed.",
                "operationId": "getApiJobCardsReportsCompletionTime",
                "description": "Requires the `job-cards:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "job-cards:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/job-cards/{id}/photos": {
            "get": {
                "tags": [
                    "Job cards"
                ],
                "summary": "The job's photographs. Ids, not bytes — fetch each through /api/files/{id}, because a job with twenty pictures would otherwise make this response tens of megabytes on a phone at a kerbside.",
                "operationId": "getApiJobCardsByIdPhotos",
                "description": "Requires the `job-cards:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "job-cards:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Job cards"
                ],
                "summary": "Attach a photograph. RAW BYTES, not base64 — a Compose camera hands the client a ByteArray on every target and base64 inflates a 4 MB photo by a third. An 'after' is refused while the job is draft or scheduled: it is almost always a mis-tagged before. Capped at 24 per job.",
                "operationId": "postApiJobCardsByIdPhotos",
                "description": "Requires the `job-cards:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "job-cards:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/job-cards/{id}/photos/{photoId}": {
            "delete": {
                "tags": [
                    "Job cards"
                ],
                "summary": "Unlink a photograph. Removes the LINK, not the stored file — a job-card screen is not where somebody should be able to destroy evidence.",
                "operationId": "deleteApiJobCardsByIdPhotosByPhotoId",
                "description": "Requires the `job-cards:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "job-cards:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    },
                    {
                        "name": "photoId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/job-cards/maintenance": {
            "get": {
                "tags": [
                    "Service contracts"
                ],
                "summary": "The tenant's contracts, with the next due date and the number of visits owed COMPUTED server-side by MaintenanceSchedule — the same object the generator uses, so the countdown cannot disagree with the date a job card appears on.",
                "operationId": "getApiJobCardsMaintenance",
                "description": "Requires the `job-cards:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "job-cards:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Service contracts"
                ],
                "summary": "Set up a contract. An unknown cadence is refused rather than defaulted: guessing would put a customer's quarterly inspection on a weekly footing.",
                "operationId": "postApiJobCardsMaintenance",
                "description": "Requires the `job-cards:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "job-cards:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "contactId": {
                                        "type": "string"
                                    },
                                    "title": {
                                        "type": "string"
                                    },
                                    "cadence": {
                                        "type": "string"
                                    },
                                    "anchorDate": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "endsOn": {
                                        "type": "null"
                                    },
                                    "active": {
                                        "type": "boolean"
                                    },
                                    "leadUserId": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "contactId": "<uuid>",
                                "title": "Monthly generator service",
                                "cadence": "monthly",
                                "anchorDate": "2026-01-31",
                                "endsOn": null,
                                "active": true,
                                "leadUserId": "<uuid>"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/job-cards/maintenance/{id}": {
            "put": {
                "tags": [
                    "Service contracts"
                ],
                "summary": "Change a contract. active=false pauses it — it raises nothing whatever its dates say, and keeps its history.",
                "operationId": "putApiJobCardsMaintenanceById",
                "description": "Requires the `job-cards:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "job-cards:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "contactId": {
                                        "type": "string"
                                    },
                                    "title": {
                                        "type": "string"
                                    },
                                    "cadence": {
                                        "type": "string"
                                    },
                                    "anchorDate": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "active": {
                                        "type": "boolean"
                                    }
                                }
                            },
                            "example": {
                                "contactId": "<uuid>",
                                "title": "Monthly generator service",
                                "cadence": "monthly",
                                "anchorDate": "2026-01-31",
                                "active": false
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "delete": {
                "tags": [
                    "Service contracts"
                ],
                "summary": "End a contract. Does not touch the job cards it has already raised — those are real work, some of it invoiced.",
                "operationId": "deleteApiJobCardsMaintenanceById",
                "description": "Requires the `job-cards:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "job-cards:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/job-cards/maintenance/{id}/raise": {
            "post": {
                "tags": [
                    "Service contracts"
                ],
                "summary": "Raise the visits this contract owes, now. EVERY owed visit, not just the newest: a monthly contract three visits behind owes three job cards. Idempotent by a partial unique index on (contract, due date), so two replicas cannot raise the same visit twice. The same code the maintenance-jobcards scheduled job runs.",
                "operationId": "postApiJobCardsMaintenanceByIdRaise",
                "description": "Requires the `job-cards:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "job-cards:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/time-tracking/entries": {
            "get": {
                "tags": [
                    "Time tracking"
                ],
                "summary": "Time entries, server-paged and server-filtered. It used to return every entry the tenant had ever logged, with no limit and no filter, on every screen load.",
                "operationId": "getApiTimeTrackingEntries",
                "description": "Requires the `time-tracking:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "time-tracking:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Time tracking"
                ],
                "summary": "Log time. Amounts accept what people paste — \"1 500,50\" and \"R1500.50\" both parse.",
                "operationId": "postApiTimeTrackingEntries",
                "description": "Requires the `time-tracking:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "time-tracking:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "projectId": {
                                        "type": "string"
                                    },
                                    "description": {
                                        "type": "string"
                                    },
                                    "workDate": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "hours": {
                                        "type": "number"
                                    },
                                    "billable": {
                                        "type": "boolean"
                                    }
                                }
                            },
                            "example": {
                                "projectId": "<uuid>",
                                "description": "Captured during the August review",
                                "workDate": "2026-09-30",
                                "hours": 2.0,
                                "billable": true
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/time-tracking/entries/{id}": {
            "put": {
                "tags": [
                    "Time tracking"
                ],
                "summary": "Correct an entry. There was no edit route at all until V261, so a mis-typed 8 hours could only be deleted and re-captured — and delete needs a permission capture does not.",
                "operationId": "putApiTimeTrackingEntriesById",
                "description": "Requires the `time-tracking:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "time-tracking:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "hours": {
                                        "type": "number"
                                    }
                                }
                            },
                            "example": {
                                "hours": 6.0
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "delete": {
                "tags": [
                    "Time tracking"
                ],
                "summary": "Delete an entry. Refused once it is billed or held by a job card.",
                "operationId": "deleteApiTimeTrackingEntriesById",
                "description": "Requires the `time-tracking:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "time-tracking:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/time-tracking/entries/{id}/approve": {
            "put": {
                "tags": [
                    "Time tracking"
                ],
                "summary": "Approve one entry. Records who approved it and when — approval used to be a bare boolean with no actor, under an invoice to a customer.",
                "operationId": "putApiTimeTrackingEntriesByIdApprove",
                "description": "Requires the `time-tracking:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "time-tracking:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "approved": {
                                        "type": "boolean"
                                    }
                                }
                            },
                            "example": {
                                "approved": true
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/time-tracking/entries/approve": {
            "post": {
                "tags": [
                    "Time tracking"
                ],
                "summary": "Approve many at once.",
                "operationId": "postApiTimeTrackingEntriesApprove",
                "description": "Requires the `time-tracking:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "time-tracking:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "entryIds": {
                                        "type": "array",
                                        "items": {
                                            "type": "string"
                                        }
                                    },
                                    "approved": {
                                        "type": "boolean"
                                    }
                                }
                            },
                            "example": {
                                "entryIds": [
                                    "<uuid>",
                                    "<uuid>"
                                ],
                                "approved": true
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/time-tracking/entries/{id}/bill": {
            "post": {
                "tags": [
                    "Time tracking"
                ],
                "summary": "Mark an entry billed. Refused for an entry a job card already holds.",
                "operationId": "postApiTimeTrackingEntriesByIdBill",
                "description": "Requires the `time-tracking:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "time-tracking:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "invoiceId": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "invoiceId": "<uuid>"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/time-tracking/projects": {
            "get": {
                "tags": [
                    "Time tracking"
                ],
                "summary": "Projects, with budget hours, budget amount and actuals against them.",
                "operationId": "getApiTimeTrackingProjects",
                "description": "Requires the `time-tracking:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "time-tracking:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Time tracking"
                ],
                "summary": "Create a project.",
                "operationId": "postApiTimeTrackingProjects",
                "description": "Requires the `time-tracking:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "time-tracking:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "contactId": {
                                        "type": "string"
                                    },
                                    "name": {
                                        "type": "string"
                                    },
                                    "hourlyRate": {
                                        "type": "number"
                                    },
                                    "budgetHours": {
                                        "type": "number"
                                    },
                                    "budgetAmount": {
                                        "type": "number"
                                    }
                                }
                            },
                            "example": {
                                "contactId": "<uuid>",
                                "name": "Mobile app phase 2",
                                "hourlyRate": 850.0,
                                "budgetHours": 320.0,
                                "budgetAmount": 272000.0
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/time-tracking/projects/{id}": {
            "put": {
                "tags": [
                    "Time tracking"
                ],
                "summary": "Update a project. Repricing changes future entries only — logged hours keep the rate they were logged at.",
                "operationId": "putApiTimeTrackingProjectsById",
                "description": "Requires the `time-tracking:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "time-tracking:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "hourlyRate": {
                                        "type": "number"
                                    }
                                }
                            },
                            "example": {
                                "hourlyRate": 900.0
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/time-tracking/projects/{id}/invoice": {
            "post": {
                "tags": [
                    "Time tracking"
                ],
                "summary": "Invoice a project's approved, unbilled hours as service lines.",
                "operationId": "postApiTimeTrackingProjectsByIdInvoice",
                "description": "Requires the `time-tracking:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "time-tracking:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "issueDate": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "terms": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "issueDate": "2026-08-31",
                                "terms": "30"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/time-tracking/timer/active": {
            "get": {
                "tags": [
                    "Time tracking"
                ],
                "summary": "The running timer, if there is one.",
                "operationId": "getApiTimeTrackingTimerActive",
                "description": "Requires the `time-tracking:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "time-tracking:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/time-tracking/timer/start": {
            "post": {
                "tags": [
                    "Time tracking"
                ],
                "summary": "Start a timer.",
                "operationId": "postApiTimeTrackingTimerStart",
                "description": "Requires the `time-tracking:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "time-tracking:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "projectId": {
                                        "type": "string"
                                    },
                                    "description": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "projectId": "<uuid>",
                                "description": "API work"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/time-tracking/timer/{id}/stop": {
            "post": {
                "tags": [
                    "Time tracking"
                ],
                "summary": "Stop a timer and write the entry.",
                "operationId": "postApiTimeTrackingTimerByIdStop",
                "description": "Requires the `time-tracking:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "time-tracking:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/time-tracking/weekly-totals": {
            "get": {
                "tags": [
                    "Time tracking"
                ],
                "summary": "The week's totals per person and per project.",
                "operationId": "getApiTimeTrackingWeeklyTotals",
                "description": "Requires the `time-tracking:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "time-tracking:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/time-tracking/projects/{id}/tasks": {
            "get": {
                "tags": [
                    "Time tracking"
                ],
                "summary": "Tasks on a project, with their charge and cost rates. `timetracking:view`.",
                "operationId": "getApiTimeTrackingProjectsByIdTasks",
                "description": "Requires the `time-tracking:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "time-tracking:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Time tracking"
                ],
                "summary": "Add a task to a project. `timetracking:create`. A rate left null is 'not set' rather than zero — a zero cost rate reports a 100% margin.",
                "operationId": "postApiTimeTrackingProjectsByIdTasks",
                "description": "Requires the `time-tracking:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "time-tracking:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string"
                                    },
                                    "chargeRate": {
                                        "type": "number"
                                    },
                                    "costRate": {
                                        "type": "number"
                                    },
                                    "active": {
                                        "type": "boolean"
                                    },
                                    "sortOrder": {
                                        "type": "integer"
                                    },
                                    "clearChargeRate": {
                                        "type": "boolean"
                                    },
                                    "clearCostRate": {
                                        "type": "boolean"
                                    }
                                }
                            },
                            "example": {
                                "name": "Site survey",
                                "chargeRate": 950.0,
                                "costRate": 480.0,
                                "active": true,
                                "sortOrder": 0,
                                "clearChargeRate": false,
                                "clearCostRate": false
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/time-tracking/projects/{id}/tasks/{taskId}": {
            "put": {
                "tags": [
                    "Time tracking"
                ],
                "summary": "Correct a task. `timetracking:edit`. Set clearChargeRate or clearCostRate to unset a rate: null cannot say 'unset'.",
                "operationId": "putApiTimeTrackingProjectsByIdTasksByTaskId",
                "description": "Requires the `time-tracking:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "time-tracking:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    },
                    {
                        "name": "taskId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string"
                                    },
                                    "chargeRate": {
                                        "type": "number"
                                    },
                                    "costRate": {
                                        "type": "number"
                                    },
                                    "active": {
                                        "type": "boolean"
                                    },
                                    "sortOrder": {
                                        "type": "integer"
                                    },
                                    "clearChargeRate": {
                                        "type": "boolean"
                                    },
                                    "clearCostRate": {
                                        "type": "boolean"
                                    }
                                }
                            },
                            "example": {
                                "name": "Site survey",
                                "chargeRate": 950.0,
                                "costRate": 480.0,
                                "active": true,
                                "sortOrder": 0,
                                "clearChargeRate": false,
                                "clearCostRate": false
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "delete": {
                "tags": [
                    "Time tracking"
                ],
                "summary": "Remove a task. `timetracking:delete`.",
                "operationId": "deleteApiTimeTrackingProjectsByIdTasksByTaskId",
                "description": "Requires the `time-tracking:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "time-tracking:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    },
                    {
                        "name": "taskId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/workflows/templates": {
            "get": {
                "tags": [
                    "Workflows"
                ],
                "summary": "The five board templates the new-board dialog offers, each with its columns and the stage every column maps to. Data in `shared`, not rows: a template is read once at creation and the board owns its columns from that moment.",
                "operationId": "getApiWorkflowsTemplates",
                "description": "Requires the `workflows:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "workflows:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/workflows/members": {
            "get": {
                "tags": [
                    "Workflows"
                ],
                "summary": "People a card can be assigned to. Four fields, not the user row — this fills a dropdown, and shipping roles, sessions and 2FA state to do that is the unbounded-list problem with a new name.",
                "operationId": "getApiWorkflowsMembers",
                "description": "Requires the `workflows:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "workflows:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/workflows/link-options": {
            "get": {
                "tags": [
                    "Workflows"
                ],
                "summary": "Matters and customers a board or a card can be linked to, as {id, name} pairs. Served here rather than from /api/time-tracking/projects and /api/contacts for the reason /api/bom/products exists: a role holding only workflows:* cannot call either, so the picker would be empty for exactly the person whose screen it is. Hours logged on a card become time entries against the matter this names.",
                "operationId": "getApiWorkflowsLinkOptions",
                "description": "Requires the `workflows:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "workflows:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/workflows/boards": {
            "get": {
                "tags": [
                    "Workflows"
                ],
                "summary": "Every board this company can see, with its columns and live counts. A board with no company is visible to all of them — the cost_centres shape. Archived boards are excluded unless asked for.",
                "operationId": "getApiWorkflowsBoards",
                "description": "Requires the `workflows:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "workflows:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Workflows"
                ],
                "summary": "Create a board from a template. The prefix is normalised upper case and must be free across the tenant, because it is half of every item number people quote to each other.",
                "operationId": "postApiWorkflowsBoards",
                "description": "Requires the `workflows:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "workflows:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string"
                                    },
                                    "keyPrefix": {
                                        "type": "string"
                                    },
                                    "description": {
                                        "type": "string"
                                    },
                                    "templateId": {
                                        "type": "string"
                                    },
                                    "contactId": {
                                        "type": "null"
                                    },
                                    "timeProjectId": {
                                        "type": "null"
                                    }
                                }
                            },
                            "example": {
                                "name": "Month-end close",
                                "keyPrefix": "CLOSE",
                                "description": "Reconciliations and reviews for one period",
                                "templateId": "month-end",
                                "contactId": null,
                                "timeProjectId": null
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/workflows/boards/{id}": {
            "get": {
                "tags": [
                    "Workflows"
                ],
                "summary": "One board, with its columns and their live counts.",
                "operationId": "getApiWorkflowsBoardsById",
                "description": "Requires the `workflows:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "workflows:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "put": {
                "tags": [
                    "Workflows"
                ],
                "summary": "Rename, re-point or archive a board, and save its columns as a WHOLE list. Whole-list because reorder, rename, add and remove happen in one dialog — four endpoints would let a board be saved with a column deleted and its replacement not yet added. A column still holding cards is refused, never orphaned.",
                "operationId": "putApiWorkflowsBoardsById",
                "description": "Requires the `workflows:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "workflows:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string"
                                    },
                                    "description": {
                                        "type": "string"
                                    },
                                    "archived": {
                                        "type": "boolean"
                                    },
                                    "columns": {
                                        "type": "array",
                                        "items": {
                                            "type": "object",
                                            "properties": {
                                                "id": {
                                                    "type": "string"
                                                },
                                                "name": {
                                                    "type": "string"
                                                },
                                                "stage": {
                                                    "type": "string"
                                                },
                                                "wipLimit": {
                                                    "type": "null"
                                                }
                                            }
                                        }
                                    }
                                }
                            },
                            "example": {
                                "name": "Month-end close",
                                "description": "Reconciliations and reviews",
                                "archived": false,
                                "columns": [
                                    {
                                        "id": "<uuid>",
                                        "name": "Not started",
                                        "stage": "todo",
                                        "wipLimit": null
                                    },
                                    {
                                        "id": null,
                                        "name": "In progress",
                                        "stage": "in_progress",
                                        "wipLimit": 5
                                    }
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "delete": {
                "tags": [
                    "Workflows"
                ],
                "summary": "Delete an EMPTY board. One still holding cards is refused with the count and told to archive instead — deleting would strand their comments, their history and any hours logged against them.",
                "operationId": "deleteApiWorkflowsBoardsById",
                "description": "Requires the `workflows:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "workflows:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/workflows/boards/{id}/items": {
            "get": {
                "tags": [
                    "Workflows"
                ],
                "summary": "The board's cards. `q` is the board's filter language — `is:` `stage:` `column:` `type:` `priority:` `label:` `assignee:` `for:` `due:` plus bare words, same key OR, different keys AND. Parsed by one object in `shared` so the client and the server cannot disagree about what `a:me` means.",
                "operationId": "getApiWorkflowsBoardsByIdItems",
                "description": "Requires the `workflows:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "workflows:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/workflows/boards/{id}/insights": {
            "get": {
                "tags": [
                    "Workflows"
                ],
                "summary": "Flow. Work in progress against each column's limit, throughput per week, and cycle time as a MEDIAN plus the 85th percentile — a mean over one card that waited four months for a supplier reports a typical card as taking three weeks, which is true of nothing on the board.",
                "operationId": "getApiWorkflowsBoardsByIdInsights",
                "description": "Requires the `workflows:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "workflows:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/workflows/boards/{id}/export": {
            "get": {
                "tags": [
                    "Workflows"
                ],
                "summary": "The board as CSV. Gated on workflows:export.",
                "operationId": "getApiWorkflowsBoardsByIdExport",
                "description": "Requires the `workflows:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "workflows:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/workflows/items": {
            "post": {
                "tags": [
                    "Workflows"
                ],
                "summary": "Raise a card. The item number comes off the board's own counter taken FOR UPDATE, never count(*), which reissues a number the moment anything is deleted. A card inherits the board's matter unless it names its own.",
                "operationId": "postApiWorkflowsItems",
                "description": "Requires the `workflows:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "workflows:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "boardId": {
                                        "type": "string"
                                    },
                                    "columnId": {
                                        "type": "null"
                                    },
                                    "title": {
                                        "type": "string"
                                    },
                                    "description": {
                                        "type": "string"
                                    },
                                    "itemType": {
                                        "type": "string"
                                    },
                                    "priority": {
                                        "type": "string"
                                    },
                                    "labels": {
                                        "type": "array",
                                        "items": {
                                            "type": "string"
                                        }
                                    },
                                    "assigneeUserId": {
                                        "type": "null"
                                    },
                                    "contactId": {
                                        "type": "null"
                                    },
                                    "timeProjectId": {
                                        "type": "null"
                                    },
                                    "estimateMinutes": {
                                        "type": "integer"
                                    },
                                    "dueDate": {
                                        "type": "string",
                                        "format": "date"
                                    }
                                }
                            },
                            "example": {
                                "boardId": "<uuid>",
                                "columnId": null,
                                "title": "Reconcile the FNB cheque account",
                                "description": "Statement to 31 August",
                                "itemType": "task",
                                "priority": "high",
                                "labels": [
                                    "vat",
                                    "fnb"
                                ],
                                "assigneeUserId": null,
                                "contactId": null,
                                "timeProjectId": null,
                                "estimateMinutes": 120,
                                "dueDate": "2026-09-07"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/workflows/items/{id}": {
            "get": {
                "tags": [
                    "Workflows"
                ],
                "summary": "One card with its comments, its history and the time logged against it. The history is read from `audit_log` rather than a second trail, so “who moved this card” has one answer. The individual time entries are included only for a caller holding timetracking:view.",
                "operationId": "getApiWorkflowsItemsById",
                "description": "Requires the `workflows:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "workflows:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "put": {
                "tags": [
                    "Workflows"
                ],
                "summary": "Edit a card. Every field optional; null leaves it alone and “” clears it — the three-state convention bills.purchase_order_id established, so a caller learns one rule rather than two.",
                "operationId": "putApiWorkflowsItemsById",
                "description": "Requires the `workflows:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "workflows:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "title": {
                                        "type": "string"
                                    },
                                    "description": {
                                        "type": "string"
                                    },
                                    "itemType": {
                                        "type": "string"
                                    },
                                    "priority": {
                                        "type": "string"
                                    },
                                    "labels": {
                                        "type": "array",
                                        "items": {
                                            "type": "string"
                                        }
                                    },
                                    "assigneeUserId": {
                                        "type": "string"
                                    },
                                    "contactId": {
                                        "type": "string"
                                    },
                                    "timeProjectId": {
                                        "type": "string"
                                    },
                                    "estimateMinutes": {
                                        "type": "integer"
                                    },
                                    "dueDate": {
                                        "type": "string",
                                        "format": "date"
                                    }
                                }
                            },
                            "example": {
                                "title": "Reconcile the FNB cheque account",
                                "description": "Statement to 31 August",
                                "itemType": "task",
                                "priority": "urgent",
                                "labels": [
                                    "vat"
                                ],
                                "assigneeUserId": "<uuid>",
                                "contactId": "",
                                "timeProjectId": "",
                                "estimateMinutes": 180,
                                "dueDate": "2026-09-07"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "delete": {
                "tags": [
                    "Workflows"
                ],
                "summary": "Soft-delete a card. Owner and Admin only by default.",
                "operationId": "deleteApiWorkflowsItemsById",
                "description": "Requires the `workflows:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "workflows:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/workflows/items/{id}/move": {
            "post": {
                "tags": [
                    "Workflows"
                ],
                "summary": "A drag. `insertIndex` rather than a rank: the client knows where the card landed between its neighbours and the server owns what number that becomes, so the fractional-index arithmetic — including renumbering an exhausted gap — lives on one side of the wire. A column at its work-in-progress limit refuses with a 409 naming the count and the limit.",
                "operationId": "postApiWorkflowsItemsByIdMove",
                "description": "Requires the `workflows:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "workflows:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "columnId": {
                                        "type": "string"
                                    },
                                    "insertIndex": {
                                        "type": "integer"
                                    }
                                }
                            },
                            "example": {
                                "columnId": "<uuid>",
                                "insertIndex": 0
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/workflows/items/{id}/comments": {
            "post": {
                "tags": [
                    "Workflows"
                ],
                "summary": "Comment on a card.",
                "operationId": "postApiWorkflowsItemsByIdComments",
                "description": "Requires the `workflows:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "workflows:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "body": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "body": "Bank has sent the missing statement."
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/workflows/comments/{id}": {
            "delete": {
                "tags": [
                    "Workflows"
                ],
                "summary": "Soft-delete a comment.",
                "operationId": "deleteApiWorkflowsCommentsById",
                "description": "Requires the `workflows:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "workflows:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/workflows/items/{id}/time": {
            "post": {
                "tags": [
                    "Workflows"
                ],
                "summary": "Log time against a card as a `time_entries` row. Gated on timetracking:create, NOT workflows:edit — the resource a permission names is the thing being written, never the screen it was written from. Refused when neither the card nor its board is linked to a project: time_entries.project_id is NOT NULL and picking one on the user's behalf would attribute somebody's afternoon to a client who never asked for it.",
                "operationId": "postApiWorkflowsItemsByIdTime",
                "description": "Requires the `workflows:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "workflows:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "minutes": {
                                        "type": "integer"
                                    },
                                    "description": {
                                        "type": "string"
                                    },
                                    "entryDate": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "billable": {
                                        "type": "boolean"
                                    }
                                }
                            },
                            "example": {
                                "minutes": 90,
                                "description": "Cleared the September reconciling items",
                                "entryDate": "2026-09-02",
                                "billable": true
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/workflows/items/{id}/attachments": {
            "get": {
                "tags": [
                    "Workflows"
                ],
                "summary": "The files filed against one card, each with a signed URL minted for this read and never stored (V237). A file that cannot be signed comes back with a null URL rather than being hidden — one unsignable file must not make the other four disappear.",
                "operationId": "getApiWorkflowsItemsByIdAttachments",
                "description": "Requires the `workflows:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "workflows:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Workflows"
                ],
                "summary": "Attach a file to a card. Raw BYTES with the name on the query string, not multipart and not base64 — base64 inflates a 4 MB scan by a third on exactly the connection this product is designed for. The declared content type is a hint used only to accept or refuse; what is stored comes from the closed list in `AttachableFile`. Twelve files a card, 15 MB each. Gated on `workflows:edit`, not `create`: the file nearly always goes onto a card that already exists.",
                "operationId": "postApiWorkflowsItemsByIdAttachments",
                "description": "Requires the `workflows:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "workflows:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/workflows/items/{id}/attachments/{linkId}": {
            "delete": {
                "tags": [
                    "Workflows"
                ],
                "summary": "Unlink a file. Removes the LINK, never the bytes — the same attachment may be referenced from an expense or a sales document, and a board is not a place from which somebody should be able to destroy a receipt the books depend on. Deleting the file itself is DELETE /api/files/{id}.",
                "operationId": "deleteApiWorkflowsItemsByIdAttachmentsByLinkId",
                "description": "Requires the `workflows:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "workflows:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    },
                    {
                        "name": "linkId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/workflows/recurrences": {
            "get": {
                "tags": [
                    "Workflows"
                ],
                "summary": "Recurring checklists — the work that comes round again. Each carries its cadence, its next due date in words (never blank: \"Paused\" and \"Finished\" are answers) and the cards it raises.",
                "operationId": "getApiWorkflowsRecurrences",
                "description": "Requires the `workflows:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "workflows:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Workflows"
                ],
                "summary": "Set up a checklist that raises itself. A month-end close is fourteen cards, not one, so the recurrence owns a LIST; each occurrence raises them as ordinary cards with nothing special about them afterwards. The cadence arithmetic is `MaintenanceSchedule`, shared with service contracts rather than copied, so a 31st anchor gives 31 Jan then 28 Feb then 31 Mar.",
                "operationId": "postApiWorkflowsRecurrences",
                "description": "Requires the `workflows:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "workflows:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/workflows/recurrences/{id}": {
            "get": {
                "tags": [
                    "Workflows"
                ],
                "summary": "One recurring checklist with its cards, in the shape the editor saves — a dialog that saves what it loaded has to load what it saves.",
                "operationId": "getApiWorkflowsRecurrencesById",
                "description": "Requires the `workflows:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "workflows:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "put": {
                "tags": [
                    "Workflows"
                ],
                "summary": "Edit a checklist. A line that keeps its id keeps its identity, and that is load-bearing: the idempotency key is (line, occurrence), so a line re-created with a fresh id would raise its card again for an occurrence already dealt with.",
                "operationId": "putApiWorkflowsRecurrencesById",
                "description": "Requires the `workflows:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "workflows:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "delete": {
                "tags": [
                    "Workflows"
                ],
                "summary": "Stop a checklist recurring. The cards it has already raised STAY — they are ordinary work somebody may be halfway through. Pausing is the reversible option and the list says so.",
                "operationId": "deleteApiWorkflowsRecurrencesById",
                "description": "Requires the `workflows:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "workflows:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/workflows/recurrences/{id}/raise": {
            "post": {
                "tags": [
                    "Workflows"
                ],
                "summary": "Raise everything this checklist owes, now — the same implementation the `workflow-recurrences` job runs, because a button with its own version would eventually raise a different set from the timer. Reports occurrences raised AND occurrences passed over as older than the backfill window: a run that reported only what it did would be indistinguishable from one that found nothing owed.",
                "operationId": "postApiWorkflowsRecurrencesByIdRaise",
                "description": "Requires the `workflows:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "workflows:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/bom/overview": {
            "get": {
                "tags": [
                    "Bills of materials"
                ],
                "summary": "The structural health of the BOM graph. Leads on what BLOCKS planning — parts flagged make-in-house with no recipe, and cycles — rather than on counts.",
                "operationId": "getApiBomOverview",
                "description": "Requires the `bom:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "bom:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/bom/{productId}/explode": {
            "get": {
                "tags": [
                    "Bills of materials"
                ],
                "summary": "Multi-level explosion for a quantity, level by level.",
                "operationId": "getApiBomByProductIdExplode",
                "description": "Requires the `bom:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "bom:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "productId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/bom/{productId}/where-used": {
            "get": {
                "tags": [
                    "Bills of materials"
                ],
                "summary": "Every parent this part appears in, at any level.",
                "operationId": "getApiBomByProductIdWhereUsed",
                "description": "Requires the `bom:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "bom:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "productId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/bom/{bomId}/detail": {
            "get": {
                "tags": [
                    "Bills of materials"
                ],
                "summary": "One BOM with its lines, effectivity and scrap factors.",
                "operationId": "getApiBomByBomIdDetail",
                "description": "Requires the `bom:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "bom:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "bomId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/bom/planning": {
            "get": {
                "tags": [
                    "Bills of materials"
                ],
                "summary": "Per-item planning parameters — make or buy, lead time, lot sizing, safety stock.",
                "operationId": "getApiBomPlanning",
                "description": "Requires the `bom:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "bom:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "put": {
                "tags": [
                    "Bills of materials"
                ],
                "summary": "Set planning parameters for a product.",
                "operationId": "putApiBomPlanning",
                "description": "Requires the `bom:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "bom:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "productId": {
                                        "type": "string"
                                    },
                                    "sourceType": {
                                        "type": "string"
                                    },
                                    "leadTimeDays": {
                                        "type": "integer"
                                    },
                                    "lotSizing": {
                                        "type": "number"
                                    },
                                    "safetyStock": {
                                        "type": "number"
                                    }
                                }
                            },
                            "example": {
                                "productId": "<uuid>",
                                "sourceType": "make",
                                "leadTimeDays": 5,
                                "lotSizing": 50.0,
                                "safetyStock": 20.0
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/bom/{bomId}/planning": {
            "put": {
                "tags": [
                    "Bills of materials"
                ],
                "summary": "Set planning parameters that belong to one BOM rather than the item.",
                "operationId": "putApiBomByBomIdPlanning",
                "description": "Requires the `bom:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "bom:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "bomId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {}
                            },
                            "example": {}
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/bom/lines/{lineId}/planning": {
            "put": {
                "tags": [
                    "Bills of materials"
                ],
                "summary": "Set a line's scrap factor and effectivity dates.",
                "operationId": "putApiBomLinesByLineIdPlanning",
                "description": "Requires the `bom:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "bom:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "lineId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "effectiveFrom": {
                                        "type": "string",
                                        "format": "date"
                                    }
                                }
                            },
                            "example": {
                                "effectiveFrom": "2026-09-01"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/bom/planning/bulk-source": {
            "post": {
                "tags": [
                    "Bills of materials"
                ],
                "summary": "Flag many items make or buy at once.",
                "operationId": "postApiBomPlanningBulkSource",
                "description": "Requires the `bom:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "bom:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "productIds": {
                                        "type": "string"
                                    },
                                    "sourceType": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "productIds": "<uuid>,<uuid>",
                                "sourceType": "buy"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/bom/products": {
            "get": {
                "tags": [
                    "Bills of materials"
                ],
                "summary": "The products this tenant can name in a recipe — id, SKU, name and whether stock is tracked. Exists because a role holding only `bom:*` cannot call /api/inventory/products, so without it the recipe editor has no way to name a finished good or a component.",
                "operationId": "getApiBomProducts",
                "description": "Requires the `bom:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "bom:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/bom/{bomId}": {
            "get": {
                "tags": [
                    "Bills of materials"
                ],
                "summary": "One recipe in the shape the EDITOR saves — every field plus the component lines. Distinct from {bomId}/detail, which is the analysis shape (roll-up, tree, where-used): a dialog that saves what it loaded has to load what it saves.",
                "operationId": "getApiBomByBomId",
                "description": "Requires the `bom:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "bom:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "bomId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "put": {
                "tags": [
                    "Bills of materials"
                ],
                "summary": "Edit a recipe. An empty `lines` list leaves the components alone; send a list to replace them. The finished good is NOT re-pointable — moving a recipe to another product would restate every build already costed against it.",
                "operationId": "putApiBomByBomId",
                "description": "Requires the `bom:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "bom:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "bomId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "productId": {
                                        "type": "string"
                                    },
                                    "name": {
                                        "type": "string"
                                    },
                                    "version": {
                                        "type": "string"
                                    },
                                    "outputQty": {
                                        "type": "number"
                                    },
                                    "labourCost": {
                                        "type": "number"
                                    },
                                    "overheadCost": {
                                        "type": "number"
                                    },
                                    "status": {
                                        "type": "string"
                                    },
                                    "notes": {
                                        "type": "string"
                                    },
                                    "effectiveFrom": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "effectiveTo": {
                                        "type": "null"
                                    },
                                    "lines": {
                                        "type": "array",
                                        "items": {
                                            "type": "object",
                                            "properties": {
                                                "componentProductId": {
                                                    "type": "string"
                                                },
                                                "qtyPer": {
                                                    "type": "number"
                                                },
                                                "scrapPercent": {
                                                    "type": "number"
                                                },
                                                "notes": {
                                                    "type": "string"
                                                },
                                                "sortOrder": {
                                                    "type": "integer"
                                                }
                                            }
                                        }
                                    }
                                }
                            },
                            "example": {
                                "productId": "<uuid>",
                                "name": "Standard build",
                                "version": "1",
                                "outputQty": 1.0,
                                "labourCost": 250.0,
                                "overheadCost": 80.0,
                                "status": "active",
                                "notes": "Two people, one shift",
                                "effectiveFrom": "2026-09-01",
                                "effectiveTo": null,
                                "lines": [
                                    {
                                        "componentProductId": "<uuid>",
                                        "qtyPer": 4.0,
                                        "scrapPercent": 2.5,
                                        "notes": "Cut to length",
                                        "sortOrder": 0
                                    }
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "delete": {
                "tags": [
                    "Bills of materials"
                ],
                "summary": "Remove a recipe — or ARCHIVE it when it has been built from, because it is then the record of how those units were costed. Gated on `bom:delete`.",
                "operationId": "deleteApiBomByBomId",
                "description": "Requires the `bom:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "bom:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "bomId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/bom": {
            "post": {
                "tags": [
                    "Bills of materials"
                ],
                "summary": "Author a recipe. Gated on `bom:create`, over the same implementation /api/inventory/boms uses — so a BOM engineer authors recipes without `inventory:create`, which also carries product creation and stock adjustments. Refuses a finished good that is not stock-tracked, a component that is the finished good, scrap at 100%, and an effectivity window that closes before it opens.",
                "operationId": "postApiBom",
                "description": "Requires the `bom:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "bom:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "productId": {
                                        "type": "string"
                                    },
                                    "name": {
                                        "type": "string"
                                    },
                                    "version": {
                                        "type": "string"
                                    },
                                    "outputQty": {
                                        "type": "number"
                                    },
                                    "labourCost": {
                                        "type": "number"
                                    },
                                    "overheadCost": {
                                        "type": "number"
                                    },
                                    "status": {
                                        "type": "string"
                                    },
                                    "notes": {
                                        "type": "string"
                                    },
                                    "effectiveFrom": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "effectiveTo": {
                                        "type": "null"
                                    },
                                    "lines": {
                                        "type": "array",
                                        "items": {
                                            "type": "object",
                                            "properties": {
                                                "componentProductId": {
                                                    "type": "string"
                                                },
                                                "qtyPer": {
                                                    "type": "number"
                                                },
                                                "scrapPercent": {
                                                    "type": "number"
                                                },
                                                "notes": {
                                                    "type": "string"
                                                },
                                                "sortOrder": {
                                                    "type": "integer"
                                                }
                                            }
                                        }
                                    }
                                }
                            },
                            "example": {
                                "productId": "<uuid>",
                                "name": "Standard build",
                                "version": "1",
                                "outputQty": 1.0,
                                "labourCost": 250.0,
                                "overheadCost": 80.0,
                                "status": "active",
                                "notes": "Two people, one shift",
                                "effectiveFrom": "2026-09-01",
                                "effectiveTo": null,
                                "lines": [
                                    {
                                        "componentProductId": "<uuid>",
                                        "qtyPer": 4.0,
                                        "scrapPercent": 2.5,
                                        "notes": "Cut to length",
                                        "sortOrder": 0
                                    }
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/bom/pending": {
            "get": {
                "tags": [
                    "Bills of materials"
                ],
                "summary": "The approver's queue — every recipe awaiting a decision, oldest submission first. Gated on `bom:view`, not `bom:approve`: an author needs to see that their own submission is still sitting there, and hiding the queue from them is how people start asking in person.",
                "operationId": "getApiBomPending",
                "description": "Requires the `bom:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "bom:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/bom/{bomId}/submit": {
            "post": {
                "tags": [
                    "Bills of materials"
                ],
                "summary": "Ask for a decision. `bom:edit` — submitting is the author's act. Refuses a recipe with no components: there is nothing to approve about one, and a button that always 400s is how people learn to ignore a workflow.",
                "operationId": "postApiBomByBomIdSubmit",
                "description": "Requires the `bom:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "bom:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "bomId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/bom/{bomId}/approve": {
            "post": {
                "tags": [
                    "Bills of materials"
                ],
                "summary": "Put a recipe into force. `bom:approve`. There is deliberately NO self-approval refusal — nothing in Ledgr has one, the control is which roles hold the permission, and a three-user tenant with one engineer would otherwise never activate a recipe. Both actors are recorded.",
                "operationId": "postApiBomByBomIdApprove",
                "description": "Requires the `bom:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "bom:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "bomId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "note": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "note": "Checked against drawing rev C"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/bom/{bomId}/reject": {
            "post": {
                "tags": [
                    "Bills of materials"
                ],
                "summary": "Send a recipe back to draft with a reason. `bom:approve`, and the reason is REQUIRED — a rejection with no reason means the author resubmits the same recipe.",
                "operationId": "postApiBomByBomIdReject",
                "description": "Requires the `bom:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "bom:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "bomId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "note": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "note": "The beam quantity is per pair, not per frame"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/mrp/runs": {
            "get": {
                "tags": [
                    "Material planning (MRP)"
                ],
                "summary": "MRP runs and their outcomes.",
                "operationId": "getApiMrpRuns",
                "description": "Requires the `mrp:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "mrp:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Material planning (MRP)"
                ],
                "summary": "Run MRP across the planning horizon.",
                "operationId": "postApiMrpRuns",
                "description": "Requires the `mrp:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "mrp:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "includeForecast": {
                                        "type": "boolean"
                                    }
                                }
                            },
                            "example": {
                                "includeForecast": true
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/mrp/runs/latest": {
            "get": {
                "tags": [
                    "Material planning (MRP)"
                ],
                "summary": "The most recent run, which is what the screen opens on.",
                "operationId": "getApiMrpRunsLatest",
                "description": "Requires the `mrp:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "mrp:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/mrp/runs/{id}": {
            "get": {
                "tags": [
                    "Material planning (MRP)"
                ],
                "summary": "One run — its planned orders, exceptions and the time-phased grid.",
                "operationId": "getApiMrpRunsById",
                "description": "Requires the `mrp:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "mrp:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/mrp/planned-orders/{id}/firm": {
            "post": {
                "tags": [
                    "Material planning (MRP)"
                ],
                "summary": "Firm a recommendation so the next run stops moving it.",
                "operationId": "postApiMrpPlannedOrdersByIdFirm",
                "description": "Requires the `mrp:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "mrp:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/mrp/planned-orders/{id}/cancel": {
            "post": {
                "tags": [
                    "Material planning (MRP)"
                ],
                "summary": "Discard a recommendation.",
                "operationId": "postApiMrpPlannedOrdersByIdCancel",
                "description": "Requires the `mrp:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "mrp:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/mrp/convert": {
            "post": {
                "tags": [
                    "Material planning (MRP)"
                ],
                "summary": "Split recommendations into requisitions for the buy side and work orders for the make side.",
                "operationId": "postApiMrpConvert",
                "description": "Requires the `mrp:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "mrp:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "plannedOrderIds": {
                                        "type": "array",
                                        "items": {
                                            "type": "string"
                                        }
                                    }
                                }
                            },
                            "example": {
                                "plannedOrderIds": [
                                    "<uuid>"
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/mrp/forecasts": {
            "get": {
                "tags": [
                    "Material planning (MRP)"
                ],
                "summary": "Demand forecasts feeding the run.",
                "operationId": "getApiMrpForecasts",
                "description": "Requires the `mrp:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "mrp:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Material planning (MRP)"
                ],
                "summary": "Add a forecast.",
                "operationId": "postApiMrpForecasts",
                "description": "Requires the `mrp:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "mrp:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "productId": {
                                        "type": "string"
                                    },
                                    "periodStart": {
                                        "type": "string"
                                    },
                                    "qty": {
                                        "type": "number"
                                    }
                                }
                            },
                            "example": {
                                "productId": "<uuid>",
                                "periodStart": "2026-10",
                                "qty": 400.0
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/mrp/forecasts/{id}": {
            "delete": {
                "tags": [
                    "Material planning (MRP)"
                ],
                "summary": "Remove a forecast.",
                "operationId": "deleteApiMrpForecastsById",
                "description": "Requires the `mrp:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "mrp:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/mrp/capacity": {
            "get": {
                "tags": [
                    "Material planning (MRP)"
                ],
                "summary": "Load against capacity per work centre across the horizon.",
                "operationId": "getApiMrpCapacity",
                "description": "Requires the `mrp:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "mrp:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/mrp/calendar": {
            "get": {
                "tags": [
                    "Material planning (MRP)"
                ],
                "summary": "The manufacturing calendar — working days, shifts and shutdowns.",
                "operationId": "getApiMrpCalendar",
                "description": "Requires the `mrp:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "mrp:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Material planning (MRP)"
                ],
                "summary": "Add a calendar entry.",
                "operationId": "postApiMrpCalendar",
                "description": "Requires the `mrp:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "mrp:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "date": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "isWorking": {
                                        "type": "boolean"
                                    },
                                    "notes": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "date": "2026-12-25",
                                "isWorking": false,
                                "notes": "Public holiday"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "delete": {
                "tags": [
                    "Material planning (MRP)"
                ],
                "summary": "Clear a range of calendar entries.",
                "operationId": "deleteApiMrpCalendar",
                "description": "Requires the `mrp:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "mrp:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/mrp/calendar/{id}": {
            "delete": {
                "tags": [
                    "Material planning (MRP)"
                ],
                "summary": "Remove one calendar entry.",
                "operationId": "deleteApiMrpCalendarById",
                "description": "Requires the `mrp:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "mrp:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/mrp/work-centres": {
            "get": {
                "tags": [
                    "Material planning (MRP)"
                ],
                "summary": "Work centres, their capacity and their rates.",
                "operationId": "getApiMrpWorkCentres",
                "description": "Requires the `mrp:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "mrp:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Material planning (MRP)"
                ],
                "summary": "Add a work centre.",
                "operationId": "postApiMrpWorkCentres",
                "description": "Requires the `mrp:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "mrp:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "code": {
                                        "type": "string"
                                    },
                                    "name": {
                                        "type": "string"
                                    },
                                    "capacityHoursPerDay": {
                                        "type": "number"
                                    },
                                    "labourRatePerHour": {
                                        "type": "number"
                                    }
                                }
                            },
                            "example": {
                                "code": "WELD-01",
                                "name": "Welding bay 1",
                                "capacityHoursPerDay": 8.0,
                                "labourRatePerHour": 420.0
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/mrp/work-centres/{id}": {
            "put": {
                "tags": [
                    "Material planning (MRP)"
                ],
                "summary": "Update a work centre.",
                "operationId": "putApiMrpWorkCentresById",
                "description": "Requires the `mrp:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "mrp:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "capacityHoursPerDay": {
                                        "type": "number"
                                    }
                                }
                            },
                            "example": {
                                "capacityHoursPerDay": 16.0
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "delete": {
                "tags": [
                    "Material planning (MRP)"
                ],
                "summary": "Remove a work centre.",
                "operationId": "deleteApiMrpWorkCentresById",
                "description": "Requires the `mrp:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "mrp:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/mrp/routings": {
            "get": {
                "tags": [
                    "Material planning (MRP)"
                ],
                "summary": "Routings — the ordered operations a part goes through.",
                "operationId": "getApiMrpRoutings",
                "description": "Requires the `mrp:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "mrp:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Material planning (MRP)"
                ],
                "summary": "Create a routing.",
                "operationId": "postApiMrpRoutings",
                "description": "Requires the `mrp:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "mrp:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "productId": {
                                        "type": "string"
                                    },
                                    "name": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "productId": "<uuid>",
                                "name": "Standard frame route"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/mrp/routings/{id}": {
            "get": {
                "tags": [
                    "Material planning (MRP)"
                ],
                "summary": "One routing and its operations.",
                "operationId": "getApiMrpRoutingsById",
                "description": "Requires the `mrp:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "mrp:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "put": {
                "tags": [
                    "Material planning (MRP)"
                ],
                "summary": "Update a routing.",
                "operationId": "putApiMrpRoutingsById",
                "description": "Requires the `mrp:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "mrp:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "name": "Standard frame route rev B"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "delete": {
                "tags": [
                    "Material planning (MRP)"
                ],
                "summary": "Delete a routing.",
                "operationId": "deleteApiMrpRoutingsById",
                "description": "Requires the `mrp:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "mrp:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/mrp/routings/{id}/operations": {
            "post": {
                "tags": [
                    "Material planning (MRP)"
                ],
                "summary": "Add an operation to a routing.",
                "operationId": "postApiMrpRoutingsByIdOperations",
                "description": "Requires the `mrp:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "mrp:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "sequenceNo": {
                                        "type": "integer"
                                    },
                                    "workCentreId": {
                                        "type": "string"
                                    },
                                    "setupHours": {
                                        "type": "number"
                                    },
                                    "runHoursPerUnit": {
                                        "type": "number"
                                    }
                                }
                            },
                            "example": {
                                "sequenceNo": 10,
                                "workCentreId": "<uuid>",
                                "setupHours": 0.5,
                                "runHoursPerUnit": 0.05
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/mrp/routings/operations/{opId}": {
            "put": {
                "tags": [
                    "Material planning (MRP)"
                ],
                "summary": "Change an operation.",
                "operationId": "putApiMrpRoutingsOperationsByOpId",
                "description": "Requires the `mrp:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "mrp:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "opId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "runHoursPerUnit": {
                                        "type": "number"
                                    }
                                }
                            },
                            "example": {
                                "runHoursPerUnit": 0.05
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "delete": {
                "tags": [
                    "Material planning (MRP)"
                ],
                "summary": "Remove an operation.",
                "operationId": "deleteApiMrpRoutingsOperationsByOpId",
                "description": "Requires the `mrp:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "mrp:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "opId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/mrp/scrap-reasons": {
            "get": {
                "tags": [
                    "Material planning (MRP)"
                ],
                "summary": "The scrap reason list.",
                "operationId": "getApiMrpScrapReasons",
                "description": "Requires the `mrp:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "mrp:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Material planning (MRP)"
                ],
                "summary": "Add a scrap reason.",
                "operationId": "postApiMrpScrapReasons",
                "description": "Requires the `mrp:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "mrp:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "code": {
                                        "type": "string"
                                    },
                                    "label": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "code": "weld-porosity",
                                "label": "Weld porosity"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/mrp/scrap-reasons/{code}": {
            "delete": {
                "tags": [
                    "Material planning (MRP)"
                ],
                "summary": "Remove a scrap reason.",
                "operationId": "deleteApiMrpScrapReasonsByCode",
                "description": "Requires the `mrp:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "mrp:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "code",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/mrp/scrap-analysis": {
            "get": {
                "tags": [
                    "Material planning (MRP)"
                ],
                "summary": "Scrap by reason, by part and by work centre.",
                "operationId": "getApiMrpScrapAnalysis",
                "description": "Requires the `mrp:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "mrp:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/mrp/batch-trace": {
            "get": {
                "tags": [
                    "Material planning (MRP)"
                ],
                "summary": "Genealogy: forward from a supplier lot to every job and finished batch that consumed it, backward from a finished batch to every lot inside it.",
                "operationId": "getApiMrpBatchTrace",
                "description": "Requires the `mrp:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "mrp:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/mrp/work-orders/{id}/genealogy": {
            "get": {
                "tags": [
                    "Material planning (MRP)"
                ],
                "summary": "The lots that went into one work order's output.",
                "operationId": "getApiMrpWorkOrdersByIdGenealogy",
                "description": "Requires the `mrp:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "mrp:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/work-orders": {
            "get": {
                "tags": [
                    "Production & work orders"
                ],
                "summary": "Work orders. The status filter lives here and nowhere else — it is the only way to reach a cancelled or completed order.",
                "operationId": "getApiWorkOrders",
                "description": "Requires the `mrp:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "mrp:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Production & work orders"
                ],
                "summary": "Raise a work order.",
                "operationId": "postApiWorkOrders",
                "description": "Requires the `mrp:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "mrp:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "productId": {
                                        "type": "string"
                                    },
                                    "qty": {
                                        "type": "number"
                                    },
                                    "plannedStart": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "plannedFinish": {
                                        "type": "string",
                                        "format": "date"
                                    }
                                }
                            },
                            "example": {
                                "productId": "<uuid>",
                                "qty": 250.0,
                                "plannedStart": "2026-09-01",
                                "plannedFinish": "2026-09-05"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/work-orders/{id}": {
            "get": {
                "tags": [
                    "Production & work orders"
                ],
                "summary": "One work order with its material, labour and cost position.",
                "operationId": "getApiWorkOrdersById",
                "description": "Requires the `mrp:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "mrp:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/work-orders/{id}/release": {
            "post": {
                "tags": [
                    "Production & work orders"
                ],
                "summary": "Release the order to the floor.",
                "operationId": "postApiWorkOrdersByIdRelease",
                "description": "Requires the `mrp:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "mrp:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/work-orders/{id}/cancel": {
            "post": {
                "tags": [
                    "Production & work orders"
                ],
                "summary": "Cancel an order and return what it holds.",
                "operationId": "postApiWorkOrdersByIdCancel",
                "description": "Requires the `mrp:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "mrp:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/work-orders/{id}/complete": {
            "post": {
                "tags": [
                    "Production & work orders"
                ],
                "summary": "Complete the order. closeJob stays a wire name — the screen says \"work order\" everywhere, but renaming the field would break the contract.",
                "operationId": "postApiWorkOrdersByIdComplete",
                "description": "Requires the `mrp:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "mrp:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "qtyCompleted": {
                                        "type": "number"
                                    },
                                    "qtyScrapped": {
                                        "type": "number"
                                    },
                                    "closeJob": {
                                        "type": "boolean"
                                    }
                                }
                            },
                            "example": {
                                "qtyCompleted": 248.0,
                                "qtyScrapped": 2.0,
                                "closeJob": true
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/work-orders/completions/{id}/reverse": {
            "post": {
                "tags": [
                    "Production & work orders"
                ],
                "summary": "Reverse a completion. Withdraws the units actually moved, not the quantity requested — the reversal path carried the same rounding defect mirrored.",
                "operationId": "postApiWorkOrdersCompletionsByIdReverse",
                "description": "Requires the `mrp:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "mrp:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "qty": {
                                        "type": "number"
                                    },
                                    "reason": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "qty": 2.0,
                                "reason": "Booked against the wrong work order"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/work-orders/{id}/issue": {
            "post": {
                "tags": [
                    "Production & work orders"
                ],
                "summary": "Issue material. With no lines, issues the recipe's requirement for the outstanding quantity, which is what a storeman picking a whole job wants. Negative quantities return material at the price the job was charged.",
                "operationId": "postApiWorkOrdersByIdIssue",
                "description": "Requires the `mrp:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "mrp:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "locationId": {
                                        "type": "string"
                                    },
                                    "lines": {
                                        "type": "array",
                                        "items": {
                                            "type": "object",
                                            "properties": {
                                                "productId": {
                                                    "type": "string"
                                                },
                                                "quantity": {
                                                    "type": "number"
                                                }
                                            }
                                        }
                                    }
                                }
                            },
                            "example": {
                                "locationId": "<uuid>",
                                "lines": [
                                    {
                                        "productId": "<uuid>",
                                        "quantity": 500.0
                                    }
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/work-orders/{id}/labour": {
            "post": {
                "tags": [
                    "Production & work orders"
                ],
                "summary": "Book hours against the order.",
                "operationId": "postApiWorkOrdersByIdLabour",
                "description": "Requires the `mrp:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "mrp:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "workCentreId": {
                                        "type": "string"
                                    },
                                    "hours": {
                                        "type": "number"
                                    },
                                    "employeeId": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "workCentreId": "<uuid>",
                                "hours": 6.0,
                                "employeeId": "<uuid>"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/work-orders/{id}/costing": {
            "get": {
                "tags": [
                    "Production & work orders"
                ],
                "summary": "The order's costing view — material, labour, overhead and the variances against standard.",
                "operationId": "getApiWorkOrdersByIdCosting",
                "description": "Requires the `mrp:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "mrp:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/work-orders/{id}/operations": {
            "get": {
                "tags": [
                    "Production & work orders"
                ],
                "summary": "The order's operations and their state.",
                "operationId": "getApiWorkOrdersByIdOperations",
                "description": "Requires the `mrp:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "mrp:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Production & work orders"
                ],
                "summary": "Attach operations to the order, usually copied from the routing.",
                "operationId": "postApiWorkOrdersByIdOperations",
                "description": "Requires the `mrp:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "mrp:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {}
                            },
                            "example": {}
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/work-orders/operations/{opId}/start": {
            "post": {
                "tags": [
                    "Production & work orders"
                ],
                "summary": "Start an operation.",
                "operationId": "postApiWorkOrdersOperationsByOpIdStart",
                "description": "Requires the `mrp:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "mrp:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "opId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/work-orders/operations/{opId}/book": {
            "post": {
                "tags": [
                    "Production & work orders"
                ],
                "summary": "Book time and quantity against an operation.",
                "operationId": "postApiWorkOrdersOperationsByOpIdBook",
                "description": "Requires the `mrp:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "mrp:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "opId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "hours": {
                                        "type": "number"
                                    },
                                    "qtyCompleted": {
                                        "type": "number"
                                    },
                                    "qtyScrapped": {
                                        "type": "number"
                                    }
                                }
                            },
                            "example": {
                                "hours": 2.5,
                                "qtyCompleted": 60.0,
                                "qtyScrapped": 1.0
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/work-orders/operations/{opId}/complete": {
            "post": {
                "tags": [
                    "Production & work orders"
                ],
                "summary": "Complete an operation.",
                "operationId": "postApiWorkOrdersOperationsByOpIdComplete",
                "description": "Requires the `mrp:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "mrp:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "opId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/work-in-progress": {
            "get": {
                "tags": [
                    "Production & work orders"
                ],
                "summary": "The open WIP balance by job. This is what reconciles the WIP control account to the floor. jobs and openJobCount stay wire names.",
                "operationId": "getApiWorkInProgress",
                "description": "Requires the `mrp:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "mrp:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/standard-costs": {
            "get": {
                "tags": [
                    "Production & work orders"
                ],
                "summary": "Stored standard-cost results.",
                "operationId": "getApiStandardCosts",
                "description": "Requires the `mrp:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "mrp:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "put": {
                "tags": [
                    "Production & work orders"
                ],
                "summary": "Override a standard cost by hand.",
                "operationId": "putApiStandardCosts",
                "description": "Requires the `mrp:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "mrp:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "productId": {
                                        "type": "string"
                                    },
                                    "materialCost": {
                                        "type": "number"
                                    }
                                }
                            },
                            "example": {
                                "productId": "<uuid>",
                                "materialCost": 1284.5
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/standard-costs/roll-up": {
            "post": {
                "tags": [
                    "Production & work orders"
                ],
                "summary": "Roll the BOM cost up through every level and store the result.",
                "operationId": "postApiStandardCostsRollUp",
                "description": "Requires the `mrp:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "mrp:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "productIds": {
                                        "type": "array",
                                        "items": {
                                            "type": "string"
                                        }
                                    }
                                }
                            },
                            "example": {
                                "productIds": [
                                    "<uuid>"
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/standard-costs/{productId}": {
            "get": {
                "tags": [
                    "Production & work orders"
                ],
                "summary": "One product's standard cost and how it was arrived at.",
                "operationId": "getApiStandardCostsByProductId",
                "description": "Requires the `mrp:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "mrp:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "productId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/procurement/overview": {
            "get": {
                "tags": [
                    "Procurement"
                ],
                "summary": "The buyer's dashboard — open requisitions, orders awaiting approval and unmatched receipts.",
                "operationId": "getApiProcurementOverview",
                "description": "Requires the `procurement:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "procurement:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/procurement/requisitions": {
            "get": {
                "tags": [
                    "Procurement"
                ],
                "summary": "Requisitions.",
                "operationId": "getApiProcurementRequisitions",
                "description": "Requires the `procurement:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "procurement:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Procurement"
                ],
                "summary": "Raise a requisition.",
                "operationId": "postApiProcurementRequisitions",
                "description": "Requires the `procurement:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "procurement:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "requiredDate": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "lines": {
                                        "type": "array",
                                        "items": {
                                            "type": "object",
                                            "properties": {
                                                "productId": {
                                                    "type": "string"
                                                },
                                                "quantity": {
                                                    "type": "number"
                                                },
                                                "estimatedUnitPrice": {
                                                    "type": "number"
                                                }
                                            }
                                        }
                                    }
                                }
                            },
                            "example": {
                                "requiredDate": "2026-09-10",
                                "lines": [
                                    {
                                        "productId": "<uuid>",
                                        "quantity": 500.0,
                                        "estimatedUnitPrice": 84.5
                                    }
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/procurement/requisitions/{id}": {
            "get": {
                "tags": [
                    "Procurement"
                ],
                "summary": "One requisition and its approval trail.",
                "operationId": "getApiProcurementRequisitionsById",
                "description": "Requires the `procurement:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "procurement:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/procurement/requisitions/{id}/submit": {
            "post": {
                "tags": [
                    "Procurement"
                ],
                "summary": "Submit for approval.",
                "operationId": "postApiProcurementRequisitionsByIdSubmit",
                "description": "Requires the `procurement:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "procurement:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/procurement/requisitions/{id}/approve": {
            "post": {
                "tags": [
                    "Procurement"
                ],
                "summary": "Approve at the caller's level in the routing.",
                "operationId": "postApiProcurementRequisitionsByIdApprove",
                "description": "Requires the `procurement:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "procurement:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/procurement/requisitions/{id}/reject": {
            "post": {
                "tags": [
                    "Procurement"
                ],
                "summary": "Reject, with a reason.",
                "operationId": "postApiProcurementRequisitionsByIdReject",
                "description": "Requires the `procurement:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "procurement:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "comment": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "comment": "Stock already on order"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/procurement/requisitions/{id}/cancel": {
            "post": {
                "tags": [
                    "Procurement"
                ],
                "summary": "Cancel a requisition.",
                "operationId": "postApiProcurementRequisitionsByIdCancel",
                "description": "Requires the `procurement:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "procurement:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/procurement/requisitions/{id}/purchase-orders": {
            "post": {
                "tags": [
                    "Procurement"
                ],
                "summary": "Turn an approved requisition into purchase orders, split by vendor.",
                "operationId": "postApiProcurementRequisitionsByIdPurchaseOrders",
                "description": "Requires the `procurement:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "procurement:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "singleSupplierId": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "singleSupplierId": "<uuid>"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/procurement/approval-rules": {
            "get": {
                "tags": [
                    "Procurement"
                ],
                "summary": "The approval routing — who approves what, at what value.",
                "operationId": "getApiProcurementApprovalRules",
                "description": "Requires the `procurement:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "procurement:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Procurement"
                ],
                "summary": "Add an approval rule.",
                "operationId": "postApiProcurementApprovalRules",
                "description": "Requires the `procurement:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "procurement:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "maxAmount": {
                                        "type": "number"
                                    },
                                    "approverRoleId": {
                                        "type": "string"
                                    },
                                    "stepNo": {
                                        "type": "integer"
                                    }
                                }
                            },
                            "example": {
                                "maxAmount": 50000.0,
                                "approverRoleId": "<uuid>",
                                "stepNo": 2
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/procurement/approval-rules/{id}": {
            "delete": {
                "tags": [
                    "Procurement"
                ],
                "summary": "Remove an approval rule.",
                "operationId": "deleteApiProcurementApprovalRulesById",
                "description": "Requires the `procurement:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "procurement:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/procurement/vendors": {
            "get": {
                "tags": [
                    "Procurement"
                ],
                "summary": "Approved vendors with their ratings and B-BBEE levels.",
                "operationId": "getApiProcurementVendors",
                "description": "Requires the `procurement:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "procurement:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "put": {
                "tags": [
                    "Procurement"
                ],
                "summary": "Approve a supplier as a vendor, or change their standing.",
                "operationId": "putApiProcurementVendors",
                "description": "Requires the `procurement:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "procurement:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "supplierId": {
                                        "type": "string"
                                    },
                                    "status": {
                                        "type": "string"
                                    },
                                    "priority": {
                                        "type": "integer"
                                    }
                                }
                            },
                            "example": {
                                "supplierId": "<uuid>",
                                "status": "approved",
                                "priority": 1
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/procurement/vendors/{id}": {
            "delete": {
                "tags": [
                    "Procurement"
                ],
                "summary": "Withdraw vendor approval.",
                "operationId": "deleteApiProcurementVendorsById",
                "description": "Requires the `procurement:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "procurement:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/procurement/vendors/unsourced": {
            "get": {
                "tags": [
                    "Procurement"
                ],
                "summary": "Parts flagged buy with no approved vendor — the list that stops an MRP run producing a usable answer.",
                "operationId": "getApiProcurementVendorsUnsourced",
                "description": "Requires the `procurement:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "procurement:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/procurement/vendors/{id}/price-breaks": {
            "get": {
                "tags": [
                    "Procurement"
                ],
                "summary": "A vendor's quantity price breaks.",
                "operationId": "getApiProcurementVendorsByIdPriceBreaks",
                "description": "Requires the `procurement:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "procurement:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "put": {
                "tags": [
                    "Procurement"
                ],
                "summary": "Set a vendor's price breaks.",
                "operationId": "putApiProcurementVendorsByIdPriceBreaks",
                "description": "Requires the `procurement:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "procurement:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "breaks": {
                                        "type": "array",
                                        "items": {
                                            "type": "object",
                                            "properties": {
                                                "minQuantity": {
                                                    "type": "number"
                                                },
                                                "unitPrice": {
                                                    "type": "number"
                                                }
                                            }
                                        }
                                    }
                                }
                            },
                            "example": {
                                "breaks": [
                                    {
                                        "minQuantity": 500.0,
                                        "unitPrice": 79.9
                                    }
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/procurement/contracts": {
            "get": {
                "tags": [
                    "Procurement"
                ],
                "summary": "Supply contracts and their coverage.",
                "operationId": "getApiProcurementContracts",
                "description": "Requires the `procurement:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "procurement:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Procurement"
                ],
                "summary": "Record a contract.",
                "operationId": "postApiProcurementContracts",
                "description": "Requires the `procurement:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "procurement:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "supplierId": {
                                        "type": "string"
                                    },
                                    "contractNumber": {
                                        "type": "string"
                                    },
                                    "startDate": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "endDate": {
                                        "type": "string",
                                        "format": "date"
                                    }
                                }
                            },
                            "example": {
                                "supplierId": "<uuid>",
                                "contractNumber": "SUP-2026-04",
                                "startDate": "2026-09-01",
                                "endDate": "2027-08-31"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/procurement/contracts/{id}/status": {
            "put": {
                "tags": [
                    "Procurement"
                ],
                "summary": "Activate, suspend or close a contract.",
                "operationId": "putApiProcurementContractsByIdStatus",
                "description": "Requires the `procurement:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "procurement:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "status": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "status": "active"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/procurement/rfqs": {
            "get": {
                "tags": [
                    "Procurement"
                ],
                "summary": "Requests for quotation.",
                "operationId": "getApiProcurementRfqs",
                "description": "Requires the `procurement:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "procurement:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Procurement"
                ],
                "summary": "Raise an RFQ.",
                "operationId": "postApiProcurementRfqs",
                "description": "Requires the `procurement:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "procurement:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "responseDueDate": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "supplierIds": {
                                        "type": "array",
                                        "items": {
                                            "type": "string"
                                        }
                                    },
                                    "lines": {
                                        "type": "array",
                                        "items": {
                                            "type": "object",
                                            "properties": {
                                                "productId": {
                                                    "type": "string"
                                                },
                                                "quantity": {
                                                    "type": "number"
                                                }
                                            }
                                        }
                                    }
                                }
                            },
                            "example": {
                                "responseDueDate": "2026-09-05",
                                "supplierIds": [
                                    "<uuid>"
                                ],
                                "lines": [
                                    {
                                        "productId": "<uuid>",
                                        "quantity": 500.0
                                    }
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/procurement/rfqs/{id}": {
            "get": {
                "tags": [
                    "Procurement"
                ],
                "summary": "One RFQ and the quotes received.",
                "operationId": "getApiProcurementRfqsById",
                "description": "Requires the `procurement:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "procurement:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/procurement/rfqs/{id}/send": {
            "post": {
                "tags": [
                    "Procurement"
                ],
                "summary": "Send the RFQ to its vendors.",
                "operationId": "postApiProcurementRfqsByIdSend",
                "description": "Requires the `procurement:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "procurement:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/procurement/rfqs/{id}/quotes": {
            "post": {
                "tags": [
                    "Procurement"
                ],
                "summary": "Capture a quote received off-platform.",
                "operationId": "postApiProcurementRfqsByIdQuotes",
                "description": "Requires the `procurement:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "procurement:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "supplierId": {
                                        "type": "string"
                                    },
                                    "lines": {
                                        "type": "array",
                                        "items": {
                                            "type": "object",
                                            "properties": {
                                                "productId": {
                                                    "type": "string"
                                                },
                                                "unitPrice": {
                                                    "type": "number"
                                                },
                                                "leadTimeDays": {
                                                    "type": "integer"
                                                }
                                            }
                                        }
                                    }
                                }
                            },
                            "example": {
                                "supplierId": "<uuid>",
                                "lines": [
                                    {
                                        "productId": "<uuid>",
                                        "unitPrice": 81.0,
                                        "leadTimeDays": 7
                                    }
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/procurement/rfqs/{id}/award": {
            "post": {
                "tags": [
                    "Procurement"
                ],
                "summary": "Award the RFQ and raise the order.",
                "operationId": "postApiProcurementRfqsByIdAward",
                "description": "Requires the `procurement:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "procurement:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "quoteId": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "quoteId": "<uuid>"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/procurement/purchase-orders": {
            "get": {
                "tags": [
                    "Procurement"
                ],
                "summary": "Purchase orders from the buyer's side, with their transmission state.",
                "operationId": "getApiProcurementPurchaseOrders",
                "description": "Requires the `procurement:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "procurement:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/procurement/purchase-orders/{id}/transmit": {
            "post": {
                "tags": [
                    "Procurement"
                ],
                "summary": "Send the order to the vendor.",
                "operationId": "postApiProcurementPurchaseOrdersByIdTransmit",
                "description": "Requires the `procurement:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "procurement:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "channel": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "channel": "email"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/procurement/matches": {
            "get": {
                "tags": [
                    "Procurement"
                ],
                "summary": "The three-way match — order against receipt against bill — and what does not agree.",
                "operationId": "getApiProcurementMatches",
                "description": "Requires the `procurement:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "procurement:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/procurement/matches/{billId}": {
            "post": {
                "tags": [
                    "Procurement"
                ],
                "summary": "Run the match for one bill.",
                "operationId": "postApiProcurementMatchesByBillId",
                "description": "Requires the `procurement:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "procurement:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "billId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/procurement/matches/{billId}/resolve": {
            "post": {
                "tags": [
                    "Procurement"
                ],
                "summary": "Accept or write off a matching difference.",
                "operationId": "postApiProcurementMatchesByBillIdResolve",
                "description": "Requires the `procurement:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "procurement:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "billId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "accept": {
                                        "type": "boolean"
                                    },
                                    "note": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "accept": true,
                                "note": "Freight not on the order"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/procurement/settings": {
            "get": {
                "tags": [
                    "Procurement"
                ],
                "summary": "Procurement settings — tolerances, required approvals, whether a receipt is mandatory before a bill.",
                "operationId": "getApiProcurementSettings",
                "description": "Requires the `procurement:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "procurement:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "put": {
                "tags": [
                    "Procurement"
                ],
                "summary": "Change the procurement settings.",
                "operationId": "putApiProcurementSettings",
                "description": "Requires the `procurement:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "procurement:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "priceTolerancePercent": {
                                        "type": "number"
                                    },
                                    "requireRequisitionForPo": {
                                        "type": "boolean"
                                    }
                                }
                            },
                            "example": {
                                "priceTolerancePercent": 2.0,
                                "requireRequisitionForPo": true
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/procurement/bbbee-spend": {
            "get": {
                "tags": [
                    "Procurement"
                ],
                "summary": "Preferential procurement spend by supplier B-BBEE level, which feeds the scorecard element.",
                "operationId": "getApiProcurementBbbeeSpend",
                "description": "Requires the `procurement:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "procurement:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/procurement/suppliers/{id}/bbbee": {
            "put": {
                "tags": [
                    "Procurement"
                ],
                "summary": "Record a supplier's B-BBEE level and certificate expiry.",
                "operationId": "putApiProcurementSuppliersByIdBbbee",
                "description": "Requires the `procurement:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "procurement:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "level": {
                                        "type": "integer"
                                    },
                                    "certificateExpiry": {
                                        "type": "string",
                                        "format": "date"
                                    }
                                }
                            },
                            "example": {
                                "level": 2,
                                "certificateExpiry": "2027-03-31"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/supplier-api/v1/session": {
            "get": {
                "tags": [
                    "Supplier portal API"
                ],
                "summary": "Who the token belongs to, which buyer they are dealing with, and what they may see.",
                "operationId": "getSupplierApiV1Session",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/supplier-api/v1/orders": {
            "get": {
                "tags": [
                    "Supplier portal API"
                ],
                "summary": "Purchase orders addressed to this supplier.",
                "operationId": "getSupplierApiV1Orders",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/supplier-api/v1/orders/{id}": {
            "get": {
                "tags": [
                    "Supplier portal API"
                ],
                "summary": "One order with its lines and delivery requirements.",
                "operationId": "getSupplierApiV1OrdersById",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/supplier-api/v1/orders/{id}/acknowledge": {
            "post": {
                "tags": [
                    "Supplier portal API"
                ],
                "summary": "Acknowledge an order, with a promised date.",
                "operationId": "postSupplierApiV1OrdersByIdAcknowledge",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "promisedDate": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "note": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "promisedDate": "2026-09-08",
                                "note": "Full quantity available"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/supplier-api/v1/rfqs": {
            "get": {
                "tags": [
                    "Supplier portal API"
                ],
                "summary": "RFQs this supplier has been invited to.",
                "operationId": "getSupplierApiV1Rfqs",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/supplier-api/v1/rfqs/{id}": {
            "get": {
                "tags": [
                    "Supplier portal API"
                ],
                "summary": "One RFQ and its lines.",
                "operationId": "getSupplierApiV1RfqsById",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/supplier-api/v1/rfqs/{id}/quote": {
            "post": {
                "tags": [
                    "Supplier portal API"
                ],
                "summary": "Submit a quote against an RFQ.",
                "operationId": "postSupplierApiV1RfqsByIdQuote",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "lines": {
                                        "type": "array",
                                        "items": {
                                            "type": "object",
                                            "properties": {
                                                "rfqLineId": {
                                                    "type": "string"
                                                },
                                                "unitPrice": {
                                                    "type": "number"
                                                },
                                                "leadTimeDays": {
                                                    "type": "integer"
                                                }
                                            }
                                        }
                                    },
                                    "validUntil": {
                                        "type": "string",
                                        "format": "date"
                                    }
                                }
                            },
                            "example": {
                                "lines": [
                                    {
                                        "rfqLineId": "<uuid>",
                                        "unitPrice": 81.0,
                                        "leadTimeDays": 7
                                    }
                                ],
                                "validUntil": "2026-09-30"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/supplier-api/v1/invoices": {
            "get": {
                "tags": [
                    "Supplier portal API"
                ],
                "summary": "Invoices this supplier has submitted, and their state.",
                "operationId": "getSupplierApiV1Invoices",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Supplier portal API"
                ],
                "summary": "Submit an invoice against an order. It arrives as a bill for the buyer to match, never as an approved payable.",
                "operationId": "postSupplierApiV1Invoices",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "purchaseOrderId": {
                                        "type": "string"
                                    },
                                    "invoiceNumber": {
                                        "type": "string"
                                    },
                                    "issueDate": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "lines": {
                                        "type": "array",
                                        "items": {
                                            "type": "object",
                                            "properties": {
                                                "poLineId": {
                                                    "type": "string"
                                                },
                                                "quantity": {
                                                    "type": "number"
                                                },
                                                "unitPrice": {
                                                    "type": "number"
                                                },
                                                "vatRate": {
                                                    "type": "number"
                                                }
                                            }
                                        }
                                    }
                                }
                            },
                            "example": {
                                "purchaseOrderId": "<uuid>",
                                "invoiceNumber": "ACME-8841",
                                "issueDate": "2026-09-09",
                                "lines": [
                                    {
                                        "poLineId": "<uuid>",
                                        "quantity": 500.0,
                                        "unitPrice": 81.0,
                                        "vatRate": 15.0
                                    }
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/supplier-api/v1/invoices/{id}/withdraw": {
            "post": {
                "tags": [
                    "Supplier portal API"
                ],
                "summary": "Withdraw a submitted invoice before the buyer processes it.",
                "operationId": "postSupplierApiV1InvoicesByIdWithdraw",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/supplier-api/v1/catalogue": {
            "get": {
                "tags": [
                    "Supplier portal API"
                ],
                "summary": "The supplier's own price list, and which of their prices the buyer is ordering against.",
                "operationId": "getSupplierApiV1Catalogue",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Supplier portal API"
                ],
                "summary": "Add or correct one price-list item. Keyed on the supplier's own SKU, so re-sending a price list updates rather than duplicating.",
                "operationId": "postSupplierApiV1Catalogue",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "description": {
                                        "type": "string"
                                    },
                                    "supplierSku": {
                                        "type": "string"
                                    },
                                    "unitPrice": {
                                        "type": "number"
                                    },
                                    "minOrderQty": {
                                        "type": "number"
                                    },
                                    "orderMultiple": {
                                        "type": "number"
                                    },
                                    "leadTimeDays": {
                                        "type": "integer"
                                    },
                                    "priceBreaks": {
                                        "type": "array",
                                        "items": {
                                            "type": "object",
                                            "properties": {
                                                "minQty": {
                                                    "type": "number"
                                                },
                                                "unitPrice": {
                                                    "type": "number"
                                                }
                                            }
                                        }
                                    }
                                }
                            },
                            "example": {
                                "description": "12mm mild steel plate",
                                "supplierSku": "MS-12-2412",
                                "unitPrice": 1840.0,
                                "minOrderQty": 4.0,
                                "orderMultiple": 4.0,
                                "leadTimeDays": 5,
                                "priceBreaks": [
                                    {
                                        "minQty": 20.0,
                                        "unitPrice": 1755.0
                                    }
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/supplier-api/v1/catalogue/{id}/withdraw": {
            "post": {
                "tags": [
                    "Supplier portal API"
                ],
                "summary": "Stop offering an item. Withdrawn rather than deleted, so a price quoted last month stays explicable.",
                "operationId": "postSupplierApiV1CatalogueByIdWithdraw",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/supplier-api/v1/compliance": {
            "get": {
                "tags": [
                    "Supplier portal API"
                ],
                "summary": "What is on file, what is about to lapse, and what the buyer has never had — the missing list included, because a supplier cannot send a document nobody asked for.",
                "operationId": "getSupplierApiV1Compliance",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Supplier portal API"
                ],
                "summary": "File a compliance document. It arrives as submitted and changes nothing until a person accepts it; a B-BBEE level is held as a CLAIM until then.",
                "operationId": "postSupplierApiV1Compliance",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "kind": {
                                        "type": "string"
                                    },
                                    "reference": {
                                        "type": "string"
                                    },
                                    "issuedOn": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "expiresOn": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "claimedBbbeeLevel": {
                                        "type": "integer"
                                    },
                                    "attachmentId": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "kind": "bbbee_certificate",
                                "reference": "VA-2026-00184",
                                "issuedOn": "2026-03-01",
                                "expiresOn": "2027-02-28",
                                "claimedBbbeeLevel": 4,
                                "attachmentId": "<uuid>"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/supplier-api/v1/compliance/upload": {
            "post": {
                "tags": [
                    "Supplier portal API"
                ],
                "summary": "Upload the scan of a document, up to 8MB. Returns an attachment id to quote on the submit. The server decides the content type, never the client.",
                "operationId": "postSupplierApiV1ComplianceUpload",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/supplier-api/v1/offers": {
            "get": {
                "tags": [
                    "Supplier portal API"
                ],
                "summary": "Offers this supplier has made unasked, with the buyer's outcome and whether each may still be revised.",
                "operationId": "getSupplierApiV1Offers",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Supplier portal API"
                ],
                "summary": "Send a price nobody asked for, or revise one already sent. Idempotent on clientReference — two prices for the same work in front of a buyer is worse than a duplicate invoice.",
                "operationId": "postSupplierApiV1Offers",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "clientReference": {
                                        "type": "string"
                                    },
                                    "title": {
                                        "type": "string"
                                    },
                                    "validUntil": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "leadTimeDays": {
                                        "type": "integer"
                                    },
                                    "lines": {
                                        "type": "array",
                                        "items": {
                                            "type": "object",
                                            "properties": {
                                                "description": {
                                                    "type": "string"
                                                },
                                                "quantity": {
                                                    "type": "number"
                                                },
                                                "unitPrice": {
                                                    "type": "number"
                                                },
                                                "vatRatePercent": {
                                                    "type": "number"
                                                }
                                            }
                                        }
                                    }
                                }
                            },
                            "example": {
                                "clientReference": "offer-4471",
                                "title": "Steelwork, Bellville warehouse",
                                "validUntil": "2026-10-15",
                                "leadTimeDays": 10,
                                "lines": [
                                    {
                                        "description": "Portal frames",
                                        "quantity": 40.0,
                                        "unitPrice": 8750.0,
                                        "vatRatePercent": 15.0
                                    }
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/supplier-api/v1/offers/{id}/withdraw": {
            "post": {
                "tags": [
                    "Supplier portal API"
                ],
                "summary": "Take an offer off the table. A different fact from the buyer rejecting it, so it does not overwrite the status.",
                "operationId": "postSupplierApiV1OffersByIdWithdraw",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/supplier-api/v1/orders/{id}/despatchable": {
            "get": {
                "tags": [
                    "Supplier portal API"
                ],
                "summary": "What may still be declared as sent on an order — netting off what has been received AND what is already in transit.",
                "operationId": "getSupplierApiV1OrdersByIdDespatchable",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/supplier-api/v1/orders/{id}/despatch": {
            "post": {
                "tags": [
                    "Supplier portal API"
                ],
                "summary": "Declare that goods have left. Moves no stock and costs nothing; it pre-fills the buyer's goods receipt and gives their expediting screen a real arrival date.",
                "operationId": "postSupplierApiV1OrdersByIdDespatch",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "clientReference": {
                                        "type": "string"
                                    },
                                    "despatchedOn": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "expectedArrival": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "carrier": {
                                        "type": "string"
                                    },
                                    "waybill": {
                                        "type": "string"
                                    },
                                    "lines": {
                                        "type": "array",
                                        "items": {
                                            "type": "object",
                                            "properties": {
                                                "poLineId": {
                                                    "type": "string"
                                                },
                                                "qty": {
                                                    "type": "number"
                                                }
                                            }
                                        }
                                    }
                                }
                            },
                            "example": {
                                "clientReference": "asn-9912",
                                "despatchedOn": "2026-09-10",
                                "expectedArrival": "2026-09-12",
                                "carrier": "Own vehicle",
                                "waybill": "WB-33417",
                                "lines": [
                                    {
                                        "poLineId": "<uuid>",
                                        "qty": 40.0
                                    }
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/supplier-api/v1/despatches": {
            "get": {
                "tags": [
                    "Supplier portal API"
                ],
                "summary": "Deliveries this supplier has declared, and whether each has been booked in.",
                "operationId": "getSupplierApiV1Despatches",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/supplier-api/v1/statement": {
            "get": {
                "tags": [
                    "Supplier portal API"
                ],
                "summary": "What the supplier is owed, when it falls due, and the payables control's verdict on anything held — from the buyer's own books.",
                "operationId": "getSupplierApiV1Statement",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/supplier-api/v1/scorecard": {
            "get": {
                "tags": [
                    "Supplier portal API"
                ],
                "summary": "The supplier's own on-time rating, quote win rate and spend. The same figures the buyer's vendor list draws — a rating a supplier cannot see is one they cannot improve.",
                "operationId": "getSupplierApiV1Scorecard",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/procurement/supplier-offers": {
            "get": {
                "tags": [
                    "Supplier workspace, buyer side"
                ],
                "summary": "Quotes suppliers sent without being asked. Kept apart from RFQ responses, which belong beside their competitors.",
                "operationId": "getApiProcurementSupplierOffers",
                "description": "Requires the `procurement:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "procurement:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/procurement/supplier-offers/{id}/decide": {
            "post": {
                "tags": [
                    "Supplier workspace, buyer side"
                ],
                "summary": "Accept or decline an offer. A reason is required on a decline and the supplier reads it — 'we went elsewhere' with no reason is why suppliers stop quoting.",
                "operationId": "postApiProcurementSupplierOffersByIdDecide",
                "description": "Requires the `procurement:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "procurement:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "outcome": {
                                        "type": "string"
                                    },
                                    "note": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "outcome": "decline",
                                "note": "Went with a shorter lead time."
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/procurement/supplier-catalogue": {
            "get": {
                "tags": [
                    "Supplier workspace, buyer side"
                ],
                "summary": "What suppliers say they sell, with their volume breaks and order multiples.",
                "operationId": "getApiProcurementSupplierCatalogue",
                "description": "Requires the `procurement:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "procurement:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/procurement/supplier-catalogue/{id}/adopt": {
            "post": {
                "tags": [
                    "Supplier workspace, buyer side"
                ],
                "summary": "Take an offered item into the approved source list. COPIES the figures, so a supplier editing their catalogue afterwards cannot move a price you have contracted at.",
                "operationId": "postApiProcurementSupplierCatalogueByIdAdopt",
                "description": "Requires the `procurement:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "procurement:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "productId": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "productId": "<uuid>"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/procurement/supplier-compliance": {
            "get": {
                "tags": [
                    "Supplier workspace, buyer side"
                ],
                "summary": "Every supplier's paperwork with its expiry and what lapses with it — the 30-day question is a WHERE clause on one date.",
                "operationId": "getApiProcurementSupplierCompliance",
                "description": "Requires the `procurement:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "procurement:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/procurement/supplier-compliance/{id}/review": {
            "post": {
                "tags": [
                    "Supplier workspace, buyer side"
                ],
                "summary": "Accept or reject a document. Accepting a B-BBEE certificate is the ONLY thing that writes contacts.bbbee_level, because an unverified level is an audit finding against the buyer who claimed the spend.",
                "operationId": "postApiProcurementSupplierComplianceByIdReview",
                "description": "Requires the `procurement:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "procurement:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "outcome": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "outcome": "accept"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/procurement/incoming-despatches": {
            "get": {
                "tags": [
                    "Supplier workspace, buyer side"
                ],
                "summary": "What suppliers say is on its way, with a real expected arrival rather than a date typed at ordering time. Nothing here has moved any stock.",
                "operationId": "getApiProcurementIncomingDespatches",
                "description": "Requires the `procurement:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "procurement:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/logistics/board": {
            "get": {
                "tags": [
                    "Logistics & fleet"
                ],
                "summary": "Every vehicle with its state, its current trip and its last known position. The screen a fleet controller leaves open.",
                "operationId": "getApiLogisticsBoard",
                "description": "Requires the `logistics:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "logistics:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/logistics/summary": {
            "get": {
                "tags": [
                    "Logistics & fleet"
                ],
                "summary": "Today's counts, plus what expires in 30 days — the licence disc, the certificate of fitness, the operator card, the driver's licence and the PrDP.",
                "operationId": "getApiLogisticsSummary",
                "description": "Requires the `logistics:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "logistics:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/logistics/performance": {
            "get": {
                "tags": [
                    "Logistics & fleet"
                ],
                "summary": "On-time delivery, failure reasons, distance and cost per trip.",
                "operationId": "getApiLogisticsPerformance",
                "description": "Requires the `logistics:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "logistics:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/logistics/unassigned": {
            "get": {
                "tags": [
                    "Logistics & fleet"
                ],
                "summary": "Documents ready to deliver that no trip has picked up.",
                "operationId": "getApiLogisticsUnassigned",
                "description": "Requires the `logistics:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "logistics:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/logistics/vehicles": {
            "get": {
                "tags": [
                    "Logistics & fleet"
                ],
                "summary": "Vehicles with their SA compliance dates as columns rather than a generic documents table.",
                "operationId": "getApiLogisticsVehicles",
                "description": "Requires the `logistics:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "logistics:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Logistics & fleet"
                ],
                "summary": "Add a vehicle. The operator card applies to goods vehicles over 3 500kg under the NRTA.",
                "operationId": "postApiLogisticsVehicles",
                "description": "Requires the `logistics:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "logistics:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "registration": {
                                        "type": "string"
                                    },
                                    "licenceDiscExpiry": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "roadworthyExpiry": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "operatorCardExpiry": {
                                        "type": "string",
                                        "format": "date"
                                    }
                                }
                            },
                            "example": {
                                "registration": "CA 123-456",
                                "licenceDiscExpiry": "2027-03-31",
                                "roadworthyExpiry": "2027-01-31",
                                "operatorCardExpiry": "2027-06-30"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/logistics/vehicles/{id}": {
            "get": {
                "tags": [
                    "Logistics & fleet"
                ],
                "summary": "One vehicle with its trip and position history.",
                "operationId": "getApiLogisticsVehiclesById",
                "description": "Requires the `logistics:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "logistics:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "put": {
                "tags": [
                    "Logistics & fleet"
                ],
                "summary": "Update a vehicle.",
                "operationId": "putApiLogisticsVehiclesById",
                "description": "Requires the `logistics:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "logistics:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "registration": {
                                        "type": "string"
                                    },
                                    "licenceDiscExpiry": {
                                        "type": "string",
                                        "format": "date"
                                    }
                                }
                            },
                            "example": {
                                "registration": "Example",
                                "licenceDiscExpiry": "2028-03-31"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "delete": {
                "tags": [
                    "Logistics & fleet"
                ],
                "summary": "Retire a vehicle.",
                "operationId": "deleteApiLogisticsVehiclesById",
                "description": "Requires the `logistics:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "logistics:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/logistics/drivers": {
            "get": {
                "tags": [
                    "Logistics & fleet"
                ],
                "summary": "Drivers with their licence code and PrDP category.",
                "operationId": "getApiLogisticsDrivers",
                "description": "Requires the `logistics:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "logistics:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Logistics & fleet"
                ],
                "summary": "Add a driver. A driver is NOT a user account — the product deliberately does not meter seats, and a seat per driver would break that.",
                "operationId": "postApiLogisticsDrivers",
                "description": "Requires the `logistics:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "logistics:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "fullName": {
                                        "type": "string"
                                    },
                                    "licenceCode": {
                                        "type": "string"
                                    },
                                    "licenceExpiry": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "prdpExpiry": {
                                        "type": "string",
                                        "format": "date"
                                    }
                                }
                            },
                            "example": {
                                "fullName": "Example",
                                "licenceCode": "EC",
                                "licenceExpiry": "2029-05-31",
                                "prdpExpiry": "2027-05-31"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/logistics/drivers/{id}": {
            "put": {
                "tags": [
                    "Logistics & fleet"
                ],
                "summary": "Update a driver.",
                "operationId": "putApiLogisticsDriversById",
                "description": "Requires the `logistics:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "logistics:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "fullName": {
                                        "type": "string"
                                    },
                                    "prdpExpiry": {
                                        "type": "string",
                                        "format": "date"
                                    }
                                }
                            },
                            "example": {
                                "fullName": "Example",
                                "prdpExpiry": "2029-05-31"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "delete": {
                "tags": [
                    "Logistics & fleet"
                ],
                "summary": "Retire a driver.",
                "operationId": "deleteApiLogisticsDriversById",
                "description": "Requires the `logistics:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "logistics:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/logistics/drivers/{id}/app-token": {
            "post": {
                "tags": [
                    "Logistics & fleet"
                ],
                "summary": "Mint a driver-app credential. Returned ONCE and never retrievable; issuing a new one retires whatever that driver is holding, by bumping drivers.portal_token_version. Add ?days= to shorten the 90-day default.",
                "operationId": "postApiLogisticsDriversByIdAppToken",
                "description": "Requires the `logistics:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "logistics:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "token": {
                                            "type": "string"
                                        },
                                        "driverId": {
                                            "type": "string"
                                        },
                                        "expiresAt": {
                                            "type": "string"
                                        }
                                    }
                                },
                                "example": {
                                    "token": "<driver-token, shown once>",
                                    "driverId": "<uuid>",
                                    "expiresAt": "2026-11-20T00:00:00Z"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/logistics/drivers/{id}/app-token/revoke": {
            "post": {
                "tags": [
                    "Logistics & fleet"
                ],
                "summary": "Retire the driver's token now. A JWT is valid until it expires by construction; nothing else can retire one already on a phone.",
                "operationId": "postApiLogisticsDriversByIdAppTokenRevoke",
                "description": "Requires the `logistics:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "logistics:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/logistics/trips": {
            "get": {
                "tags": [
                    "Logistics & fleet"
                ],
                "summary": "Trips with their stops and state.",
                "operationId": "getApiLogisticsTrips",
                "description": "Requires the `logistics:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "logistics:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Logistics & fleet"
                ],
                "summary": "Plan a trip — one vehicle, one driver, one run. Numbered from the shared document_sequences series under doc_type 'trip'.",
                "operationId": "postApiLogisticsTrips",
                "description": "Requires the `logistics:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "logistics:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "tripDate": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "vehicleId": {
                                        "type": "string"
                                    },
                                    "driverId": {
                                        "type": "string"
                                    },
                                    "routeName": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "tripDate": "2026-08-25",
                                "vehicleId": "<uuid>",
                                "driverId": "<uuid>",
                                "routeName": "Northern suburbs"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/logistics/trips/{id}": {
            "get": {
                "tags": [
                    "Logistics & fleet"
                ],
                "summary": "One trip with its ordered stops.",
                "operationId": "getApiLogisticsTripsById",
                "description": "Requires the `logistics:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "logistics:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "put": {
                "tags": [
                    "Logistics & fleet"
                ],
                "summary": "Change a trip's vehicle, driver or date.",
                "operationId": "putApiLogisticsTripsById",
                "description": "Requires the `logistics:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "logistics:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "tripDate": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "driverId": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "tripDate": "2026-09-30",
                                "driverId": "<uuid>"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "delete": {
                "tags": [
                    "Logistics & fleet"
                ],
                "summary": "Delete a planned trip.",
                "operationId": "deleteApiLogisticsTripsById",
                "description": "Requires the `logistics:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "logistics:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/logistics/trips/{id}/status": {
            "post": {
                "tags": [
                    "Logistics & fleet"
                ],
                "summary": "Move a trip through planned, loading, dispatched, in_progress, completed. Departure and completion both require an odometer reading.",
                "operationId": "postApiLogisticsTripsByIdStatus",
                "description": "Requires the `logistics:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "logistics:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "status": {
                                        "type": "string"
                                    },
                                    "odometerKm": {
                                        "type": "number"
                                    }
                                }
                            },
                            "example": {
                                "status": "in_progress",
                                "odometerKm": 184320.0
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/logistics/trips/{id}/stops": {
            "post": {
                "tags": [
                    "Logistics & fleet"
                ],
                "summary": "Add a stop that is not a document — a collection, a return, a site visit or the depot leg.",
                "operationId": "postApiLogisticsTripsByIdStops",
                "description": "Requires the `logistics:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "logistics:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "stopType": {
                                        "type": "string"
                                    },
                                    "contactName": {
                                        "type": "string"
                                    },
                                    "address": {
                                        "type": "string"
                                    },
                                    "city": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "stopType": "collection",
                                "contactName": "Depot",
                                "address": "12 Marine Drive",
                                "city": "Cape Town"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/logistics/trips/{id}/stops/from-document": {
            "post": {
                "tags": [
                    "Logistics & fleet"
                ],
                "summary": "Add a stop built from a delivery note, invoice, pro-forma or purchase order — all three live in invoices, so one column covers them. Carries the customer, the address and every LINE, which is what lets a short delivery raise a credit note later.",
                "operationId": "postApiLogisticsTripsByIdStopsFromDocument",
                "description": "Requires the `logistics:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "logistics:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "documentId": {
                                        "type": "string"
                                    },
                                    "windowFrom": {
                                        "type": "string"
                                    },
                                    "windowTo": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "documentId": "<uuid>",
                                "windowFrom": "2026-08-25T09:00:00Z",
                                "windowTo": "2026-08-25T12:00:00Z"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/logistics/trips/{id}/suggested-sequence": {
            "get": {
                "tags": [
                    "Logistics & fleet"
                ],
                "summary": "A shorter order for the run, as advice. Read-only: it reorders nothing. Distances are straight-line between the stops' own coordinates rather than a road route, so the saving is reported and the planner decides; post the ids to /resequence to accept it. Refuses with a reason when the trip has already started, has fewer than three stops, or has fewer than three stops carrying coordinates.",
                "operationId": "getApiLogisticsTripsByIdSuggestedSequence",
                "description": "Requires the `logistics:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "logistics:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/logistics/trips/{id}/resequence": {
            "post": {
                "tags": [
                    "Logistics & fleet"
                ],
                "summary": "Reorder the stops.",
                "operationId": "postApiLogisticsTripsByIdResequence",
                "description": "Requires the `logistics:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "logistics:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "stopIds": {
                                        "type": "array",
                                        "items": {
                                            "type": "string"
                                        }
                                    }
                                }
                            },
                            "example": {
                                "stopIds": [
                                    "<uuid>",
                                    "<uuid>"
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/logistics/trips/{id}/track": {
            "get": {
                "tags": [
                    "Logistics & fleet"
                ],
                "summary": "The trip replay — every stored position in order.",
                "operationId": "getApiLogisticsTripsByIdTrack",
                "description": "Requires the `logistics:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "logistics:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/logistics/stops/{id}": {
            "get": {
                "tags": [
                    "Logistics & fleet"
                ],
                "summary": "One stop with its lines and window.",
                "operationId": "getApiLogisticsStopsById",
                "description": "Requires the `logistics:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "logistics:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "put": {
                "tags": [
                    "Logistics & fleet"
                ],
                "summary": "Change a stop.",
                "operationId": "putApiLogisticsStopsById",
                "description": "Requires the `logistics:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "logistics:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "windowTo": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "windowTo": "2026-08-25T14:00:00Z"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "delete": {
                "tags": [
                    "Logistics & fleet"
                ],
                "summary": "Remove a stop.",
                "operationId": "deleteApiLogisticsStopsById",
                "description": "Requires the `logistics:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "logistics:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/logistics/stops/{id}/proof": {
            "get": {
                "tags": [
                    "Logistics & fleet"
                ],
                "summary": "The proof recorded at a stop, with its grade.",
                "operationId": "getApiLogisticsStopsByIdProof",
                "description": "Requires the `logistics:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "logistics:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Logistics & fleet"
                ],
                "summary": "Record the outcome — who received it, the quantities that actually changed hands, and a drawn signature. Where the per-line quantities disagree with the tapped outcome, THE LINES WIN, and a blank line counts as not delivered. distanceFromStopM is computed server-side from two coordinate pairs and never accepted from a client.",
                "operationId": "postApiLogisticsStopsByIdProof",
                "description": "Requires the `logistics:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "logistics:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "outcome": {
                                        "type": "string"
                                    },
                                    "receivedByName": {
                                        "type": "string"
                                    },
                                    "capturedLatitude": {
                                        "type": "number"
                                    },
                                    "capturedLongitude": {
                                        "type": "number"
                                    },
                                    "capturedAccuracyM": {
                                        "type": "number"
                                    },
                                    "clientReference": {
                                        "type": "string"
                                    },
                                    "lines": {
                                        "type": "array",
                                        "items": {
                                            "type": "object",
                                            "properties": {
                                                "stopLineId": {
                                                    "type": "string"
                                                },
                                                "qtyDelivered": {
                                                    "type": "number"
                                                }
                                            }
                                        }
                                    }
                                }
                            },
                            "example": {
                                "outcome": "delivered",
                                "receivedByName": "T. Nkosi",
                                "capturedLatitude": -33.9249,
                                "capturedLongitude": 18.4241,
                                "capturedAccuracyM": 12.0,
                                "clientReference": "<client-minted-uuid>",
                                "lines": [
                                    {
                                        "stopLineId": "<uuid>",
                                        "qtyDelivered": 4.0
                                    }
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "id": {
                                            "type": "string"
                                        },
                                        "outcome": {
                                            "type": "string"
                                        },
                                        "grade": {
                                            "type": "string"
                                        },
                                        "gradeStatement": {
                                            "type": "string"
                                        },
                                        "distanceFromStopM": {
                                            "type": "number"
                                        },
                                        "capturedAt": {
                                            "type": "string"
                                        },
                                        "recordedAt": {
                                            "type": "string"
                                        }
                                    }
                                },
                                "example": {
                                    "id": "<uuid>",
                                    "outcome": "delivered",
                                    "grade": "STRONG",
                                    "gradeStatement": "Signed for by a named person at the delivery address, inside the geofence, with a photograph.",
                                    "distanceFromStopM": 18.4,
                                    "capturedAt": "2026-08-25T09:52:00Z",
                                    "recordedAt": "2026-08-25T09:52:04Z"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/logistics/proofs/{id}/photos": {
            "post": {
                "tags": [
                    "Logistics & fleet"
                ],
                "summary": "Attach a photograph to a proof. At most six; the server recompresses.",
                "operationId": "postApiLogisticsProofsByIdPhotos",
                "description": "Requires the `logistics:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "logistics:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/logistics/proofs/{id}/html": {
            "get": {
                "tags": [
                    "Logistics & fleet"
                ],
                "summary": "The proof as a document — who signed, where they stood, when, the quantities, and an honest statement of what the evidence is worth. Do not improve a weak grade's wording.",
                "operationId": "getApiLogisticsProofsByIdHtml",
                "description": "Requires the `logistics:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "logistics:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/logistics/exception-reasons": {
            "get": {
                "tags": [
                    "Logistics & fleet"
                ],
                "summary": "The failure reason list. The seeded set carries LOAD SHEDDING — the most common reason a South African delivery is turned away, and one no other product's standard list has.",
                "operationId": "getApiLogisticsExceptionReasons",
                "description": "Requires the `logistics:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "logistics:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Logistics & fleet"
                ],
                "summary": "Add a reason code.",
                "operationId": "postApiLogisticsExceptionReasons",
                "description": "Requires the `logistics:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "logistics:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "code": {
                                        "type": "string"
                                    },
                                    "label": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "code": "site_closed",
                                "label": "Site closed on arrival"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/logistics/exception-reasons/{id}": {
            "put": {
                "tags": [
                    "Logistics & fleet"
                ],
                "summary": "Relabel or retire a reason code.",
                "operationId": "putApiLogisticsExceptionReasonsById",
                "description": "Requires the `logistics:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "logistics:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "code": {
                                        "type": "string"
                                    },
                                    "label": {
                                        "type": "string"
                                    },
                                    "active": {
                                        "type": "boolean"
                                    }
                                }
                            },
                            "example": {
                                "code": "SKU-1001",
                                "label": "Example",
                                "active": true
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/logistics/positions": {
            "post": {
                "tags": [
                    "Logistics & fleet"
                ],
                "summary": "Post GPS fixes from a third-party tracker. Identify the vehicle by Ledgr's id or by telematicsExternalId, which is the id your own platform already knows it by. A 60s floor is enforced against the previous fix's DEVICE time, future-dated fixes are refused, a batch caps at 500, and what was dropped is reported.",
                "operationId": "postApiLogisticsPositions",
                "description": "Requires the `logistics:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "logistics:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "telematicsExternalId": {
                                        "type": "string"
                                    },
                                    "source": {
                                        "type": "string"
                                    },
                                    "positions": {
                                        "type": "array",
                                        "items": {
                                            "type": "object",
                                            "properties": {
                                                "latitude": {
                                                    "type": "number"
                                                },
                                                "longitude": {
                                                    "type": "number"
                                                },
                                                "speedKph": {
                                                    "type": "number"
                                                },
                                                "recordedAt": {
                                                    "type": "string"
                                                }
                                            }
                                        }
                                    }
                                }
                            },
                            "example": {
                                "telematicsExternalId": "TRK-4092",
                                "source": "telematics",
                                "positions": [
                                    {
                                        "latitude": -33.9249,
                                        "longitude": 18.4241,
                                        "speedKph": 62.0,
                                        "recordedAt": "2026-08-25T09:14:00Z"
                                    }
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "accepted": {
                                            "type": "integer"
                                        },
                                        "droppedTooFrequent": {
                                            "type": "integer"
                                        },
                                        "droppedFutureDated": {
                                            "type": "integer"
                                        },
                                        "droppedNullIsland": {
                                            "type": "integer"
                                        }
                                    }
                                },
                                "example": {
                                    "accepted": 1,
                                    "droppedTooFrequent": 0,
                                    "droppedFutureDated": 0,
                                    "droppedNullIsland": 0
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/logistics/fleet-map": {
            "get": {
                "tags": [
                    "Logistics & fleet"
                ],
                "summary": "The fleet map's framed backdrop plus the vehicles to draw on it. Send the pixel size you will render at — the server picks the zoom that frames every reporting vehicle inside exactly that box, and the client places markers with the same Web Mercator projection. Answers available=false with a reason, rather than failing, when no maps key is configured.",
                "operationId": "getApiLogisticsFleetMap",
                "description": "Requires the `logistics:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "logistics:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/maps/static": {
            "get": {
                "tags": [
                    "Logistics & fleet"
                ],
                "summary": "The street-map image itself, proxied so Google's key never reaches a client. Unauthenticated by construction: the HMAC in the query string is the credential and it covers the centre, zoom and size it authorises, so a signed URL cannot be edited into a different request. Registered only when a maps key is configured. Not called directly — GET /api/logistics/fleet-map mints the signed URL.",
                "operationId": "getMapsStatic",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/driver-api/v1/session": {
            "get": {
                "tags": [
                    "Driver app API"
                ],
                "summary": "Who the token belongs to, what vehicle they usually drive, how often to send a position, and the SERVER's own clock so the app can measure its skew. Send Authorization: Bearer <driver token> — not a user JWT.",
                "operationId": "getDriverApiV1Session",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/driver-api/v1/trips": {
            "get": {
                "tags": [
                    "Driver app API"
                ],
                "summary": "The trips this driver may act on. Each manifest carries its stops, and each stop its lines, its address and its phone number.",
                "operationId": "getDriverApiV1Trips",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/driver-api/v1/trips/{id}": {
            "get": {
                "tags": [
                    "Driver app API"
                ],
                "summary": "One manifest.",
                "operationId": "getDriverApiV1TripsById",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/driver-api/v1/trips/{id}/start": {
            "post": {
                "tags": [
                    "Driver app API"
                ],
                "summary": "Depart, with the odometer the trip's distance is measured from.",
                "operationId": "postDriverApiV1TripsByIdStart",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "odometerKm": {
                                        "type": "number"
                                    }
                                }
                            },
                            "example": {
                                "odometerKm": 184320.0
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/driver-api/v1/trips/{id}/complete": {
            "post": {
                "tags": [
                    "Driver app API"
                ],
                "summary": "Back at the depot. REFUSED while any stop still has no outcome — a trip closed with open stops leaves deliveries neither delivered nor failed, and therefore invisible to both the re-plan list and the failure report.",
                "operationId": "postDriverApiV1TripsByIdComplete",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "odometerKm": {
                                        "type": "number"
                                    },
                                    "fuelLitres": {
                                        "type": "number"
                                    },
                                    "fuelCost": {
                                        "type": "number"
                                    }
                                }
                            },
                            "example": {
                                "odometerKm": 184498.0,
                                "fuelLitres": 62.4,
                                "fuelCost": 1490.0
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/driver-api/v1/stops/{id}/status": {
            "post": {
                "tags": [
                    "Driver app API"
                ],
                "summary": "Mark a stop en route or arrived. The fix at arrival is the first half of the evidence; the geofence allows the device's own claimed error, capped at 150m, because failing an honest driver in a yard with a bad sky view is how a business learns to ignore the flag.",
                "operationId": "postDriverApiV1StopsByIdStatus",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "status": {
                                        "type": "string"
                                    },
                                    "latitude": {
                                        "type": "number"
                                    },
                                    "longitude": {
                                        "type": "number"
                                    },
                                    "accuracyM": {
                                        "type": "number"
                                    },
                                    "at": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "status": "arrived",
                                "latitude": -33.9249,
                                "longitude": 18.4241,
                                "accuracyM": 9.0,
                                "at": "2026-08-25T09:41:00Z"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/driver-api/v1/stops/{id}/proof": {
            "post": {
                "tags": [
                    "Driver app API"
                ],
                "summary": "The proof as one offline-safe unit — outcome, receiver, quantities and the drawn signature in a single request. Send the same clientReference on a retry and the STORED proof comes back rather than a second one. captured_at is your clock, recorded_at is the server's; both are kept.",
                "operationId": "postDriverApiV1StopsByIdProof",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "outcome": {
                                        "type": "string"
                                    },
                                    "receivedByName": {
                                        "type": "string"
                                    },
                                    "signatureBase64": {
                                        "type": "string"
                                    },
                                    "exceptionReasonCode": {
                                        "type": "string"
                                    },
                                    "capturedLatitude": {
                                        "type": "number"
                                    },
                                    "capturedLongitude": {
                                        "type": "number"
                                    },
                                    "capturedAt": {
                                        "type": "string"
                                    },
                                    "clientReference": {
                                        "type": "string"
                                    },
                                    "lines": {
                                        "type": "array",
                                        "items": {
                                            "type": "object",
                                            "properties": {
                                                "stopLineId": {
                                                    "type": "string"
                                                },
                                                "qtyDelivered": {
                                                    "type": "number"
                                                }
                                            }
                                        }
                                    }
                                }
                            },
                            "example": {
                                "outcome": "partial",
                                "receivedByName": "T. Nkosi",
                                "signatureBase64": "iVBORw0KGgo…",
                                "exceptionReasonCode": "short_delivered",
                                "capturedLatitude": -33.9249,
                                "capturedLongitude": 18.4241,
                                "capturedAt": "2026-08-25T09:52:00Z",
                                "clientReference": "<client-minted-uuid>",
                                "lines": [
                                    {
                                        "stopLineId": "<uuid>",
                                        "qtyDelivered": 3.0
                                    }
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/driver-api/v1/stops/{id}/proof/photos": {
            "post": {
                "tags": [
                    "Driver app API"
                ],
                "summary": "Attach a photograph to the proof just recorded. Raw image bytes as the body, ?caption= for the label.",
                "operationId": "postDriverApiV1StopsByIdProofPhotos",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/driver-api/v1/positions": {
            "post": {
                "tags": [
                    "Driver app API"
                ],
                "summary": "A batch of fixes, which is how a phone out of signal catches up. The vehicle comes from the driver's open trip and can never be named by the app.",
                "operationId": "postDriverApiV1Positions",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "positions": {
                                        "type": "array",
                                        "items": {
                                            "type": "object",
                                            "properties": {
                                                "latitude": {
                                                    "type": "number"
                                                },
                                                "longitude": {
                                                    "type": "number"
                                                },
                                                "speedKph": {
                                                    "type": "number"
                                                },
                                                "batteryPercent": {
                                                    "type": "integer"
                                                },
                                                "recordedAt": {
                                                    "type": "string"
                                                }
                                            }
                                        }
                                    }
                                }
                            },
                            "example": {
                                "positions": [
                                    {
                                        "latitude": -33.9249,
                                        "longitude": 18.4241,
                                        "speedKph": 48.0,
                                        "batteryPercent": 62,
                                        "recordedAt": "2026-08-25T09:20:00Z"
                                    }
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/driver-api/v1/exception-reasons": {
            "get": {
                "tags": [
                    "Driver app API"
                ],
                "summary": "The failure code list, for offline caching. A driver with no signal and no reason list types \"other\" into a notes field, and a year later the failure report is a reading exercise.",
                "operationId": "getDriverApiV1ExceptionReasons",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/companies": {
            "get": {
                "tags": [
                    "Multi-company & consolidation"
                ],
                "summary": "Every entity under the tenant. This is the list X-Company-Id is chosen from.",
                "operationId": "getApiCompanies",
                "description": "Requires the `multi-company:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "multi-company:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Multi-company & consolidation"
                ],
                "summary": "Add an entity. Companies are NEVER metered — that is the whole competitive argument against Sage's R410 per company.",
                "operationId": "postApiCompanies",
                "description": "Requires the `multi-company:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "multi-company:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string"
                                    },
                                    "registrationNumber": {
                                        "type": "string"
                                    },
                                    "vatNumber": {
                                        "type": "string"
                                    },
                                    "parentCompanyId": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "name": "KMP Cloud (Pty) Ltd",
                                "registrationNumber": "2021/123456/07",
                                "vatNumber": "4123456789",
                                "parentCompanyId": "<uuid>"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/companies/{id}": {
            "get": {
                "tags": [
                    "Multi-company & consolidation"
                ],
                "summary": "One entity.",
                "operationId": "getApiCompaniesById",
                "description": "Requires the `multi-company:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "multi-company:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "put": {
                "tags": [
                    "Multi-company & consolidation"
                ],
                "summary": "Update an entity.",
                "operationId": "putApiCompaniesById",
                "description": "Requires the `multi-company:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "multi-company:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "name": "KMP Cloud (Pty) Ltd"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "delete": {
                "tags": [
                    "Multi-company & consolidation"
                ],
                "summary": "Remove an entity.",
                "operationId": "deleteApiCompaniesById",
                "description": "Requires the `multi-company:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "multi-company:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/companies/{id}/users": {
            "get": {
                "tags": [
                    "Multi-company & consolidation"
                ],
                "summary": "Who may act in this entity.",
                "operationId": "getApiCompaniesByIdUsers",
                "description": "Requires the `multi-company:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "multi-company:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Multi-company & consolidation"
                ],
                "summary": "Grant a user access to this entity. An EMPTY company_users set grants — an owner whose assignments were never seeded must not be locked out.",
                "operationId": "postApiCompaniesByIdUsers",
                "description": "Requires the `multi-company:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "multi-company:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "userId": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "userId": "<uuid>"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/company-hierarchy/tree": {
            "get": {
                "tags": [
                    "Multi-company & consolidation"
                ],
                "summary": "The group structure as a tree, parent to subsidiary.",
                "operationId": "getApiCompanyHierarchyTree",
                "description": "Requires the `multi-company:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "multi-company:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/company-hierarchy/picker": {
            "get": {
                "tags": [
                    "Multi-company & consolidation"
                ],
                "summary": "A searchable flat list for the entity switcher.",
                "operationId": "getApiCompanyHierarchyPicker",
                "description": "Requires the `multi-company:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "multi-company:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/company-hierarchy/{id}/ancestors": {
            "get": {
                "tags": [
                    "Multi-company & consolidation"
                ],
                "summary": "The chain from an entity up to the group head.",
                "operationId": "getApiCompanyHierarchyByIdAncestors",
                "description": "Requires the `multi-company:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "multi-company:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/company-hierarchy/{id}/parent": {
            "put": {
                "tags": [
                    "Multi-company & consolidation"
                ],
                "summary": "Re-parent an entity. Refused if it would create a cycle.",
                "operationId": "putApiCompanyHierarchyByIdParent",
                "description": "Requires the `multi-company:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "multi-company:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "parentCompanyId": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "parentCompanyId": "<uuid>"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/company-hierarchy/repair": {
            "post": {
                "tags": [
                    "Multi-company & consolidation"
                ],
                "summary": "Repair an inconsistent hierarchy — orphans and broken depth values.",
                "operationId": "postApiCompanyHierarchyRepair",
                "description": "Requires the `multi-company:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "multi-company:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/company-hierarchy/{id}/consolidated/income-statement": {
            "get": {
                "tags": [
                    "Multi-company & consolidation"
                ],
                "summary": "Consolidated income statement at ANY node of the tree, not only the group head.",
                "operationId": "getApiCompanyHierarchyByIdConsolidatedIncomeStatement",
                "description": "Requires the `multi-company:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "multi-company:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/company-hierarchy/{id}/consolidated/balance-sheet": {
            "get": {
                "tags": [
                    "Multi-company & consolidation"
                ],
                "summary": "Consolidated balance sheet at any node.",
                "operationId": "getApiCompanyHierarchyByIdConsolidatedBalanceSheet",
                "description": "Requires the `multi-company:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "multi-company:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/company-hierarchy/{id}/consolidated/trial-balance": {
            "get": {
                "tags": [
                    "Multi-company & consolidation"
                ],
                "summary": "Consolidated trial balance at any node.",
                "operationId": "getApiCompanyHierarchyByIdConsolidatedTrialBalance",
                "description": "Requires the `multi-company:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "multi-company:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/company-hierarchy/{id}/consolidated/eliminations": {
            "get": {
                "tags": [
                    "Multi-company & consolidation"
                ],
                "summary": "The inter-company eliminations applied, line by line.",
                "operationId": "getApiCompanyHierarchyByIdConsolidatedEliminations",
                "description": "Requires the `multi-company:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "multi-company:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/intercompany-transactions": {
            "get": {
                "tags": [
                    "Multi-company & consolidation"
                ],
                "summary": "The inter-company transaction log.",
                "operationId": "getApiIntercompanyTransactions",
                "description": "Requires the `multi-company:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "multi-company:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Multi-company & consolidation"
                ],
                "summary": "Record an inter-company transaction.",
                "operationId": "postApiIntercompanyTransactions",
                "description": "Requires the `multi-company:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "multi-company:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "fromCompanyId": {
                                        "type": "string"
                                    },
                                    "toCompanyId": {
                                        "type": "string"
                                    },
                                    "transactionDate": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "amount": {
                                        "type": "number"
                                    },
                                    "description": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "fromCompanyId": "<uuid>",
                                "toCompanyId": "<uuid>",
                                "transactionDate": "2026-08-31",
                                "amount": 85000.0,
                                "description": "Management fee"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/intercompany-transactions/{id}": {
            "get": {
                "tags": [
                    "Multi-company & consolidation"
                ],
                "summary": "One transaction.",
                "operationId": "getApiIntercompanyTransactionsById",
                "description": "Requires the `multi-company:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "multi-company:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "put": {
                "tags": [
                    "Multi-company & consolidation"
                ],
                "summary": "Correct a transaction. Every field is replaced; an eliminated transaction is refused.",
                "operationId": "putApiIntercompanyTransactionsById",
                "description": "Requires the `multi-company:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "multi-company:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "fromCompanyId": {
                                        "type": "string"
                                    },
                                    "toCompanyId": {
                                        "type": "string"
                                    },
                                    "transactionDate": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "description": {
                                        "type": "string"
                                    },
                                    "amount": {
                                        "type": "number"
                                    },
                                    "currency": {
                                        "type": "string"
                                    },
                                    "exchangeRate": {
                                        "type": "number"
                                    },
                                    "accountIdFrom": {
                                        "type": "string"
                                    },
                                    "accountIdTo": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "fromCompanyId": "<uuid>",
                                "toCompanyId": "<uuid>",
                                "transactionDate": "2026-08-27",
                                "description": "Management fee - corrected",
                                "amount": 90000.0,
                                "currency": "ZAR",
                                "exchangeRate": 1.0,
                                "accountIdFrom": "<uuid>",
                                "accountIdTo": "<uuid>"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "delete": {
                "tags": [
                    "Multi-company & consolidation"
                ],
                "summary": "Delete a transaction.",
                "operationId": "deleteApiIntercompanyTransactionsById",
                "description": "Requires the `multi-company:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "multi-company:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/intercompany-transactions/{id}/match": {
            "post": {
                "tags": [
                    "Multi-company & consolidation"
                ],
                "summary": "Agree both legs of a transaction. PENDING only.",
                "operationId": "postApiIntercompanyTransactionsByIdMatch",
                "description": "Requires the `multi-company:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "multi-company:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/intercompany-transactions/{id}/settle": {
            "post": {
                "tags": [
                    "Multi-company & consolidation"
                ],
                "summary": "Eliminate an inter-company transaction for consolidation. MATCHED only. Wire alias of /{id}/eliminate; it does NOT record that one entity paid another - that is POST /{id}/payment.",
                "operationId": "postApiIntercompanyTransactionsByIdSettle",
                "description": "Requires the `multi-company:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "multi-company:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/intercompany-transactions/{id}/eliminate": {
            "post": {
                "tags": [
                    "Multi-company & consolidation"
                ],
                "summary": "Eliminate an inter-company transaction for consolidation. MATCHED only.",
                "operationId": "postApiIntercompanyTransactionsByIdEliminate",
                "description": "Requires the `multi-company:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "multi-company:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/intercompany-transactions/{id}/payment": {
            "post": {
                "tags": [
                    "Multi-company & consolidation"
                ],
                "summary": "Record that one entity actually paid another. Separate from elimination - an amount is often eliminated and still unpaid.",
                "operationId": "postApiIntercompanyTransactionsByIdPayment",
                "description": "Requires the `multi-company:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "multi-company:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "paidOn": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "reference": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "paidOn": "2026-08-25",
                                "reference": "EFT 4471"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "delete": {
                "tags": [
                    "Multi-company & consolidation"
                ],
                "summary": "Remove a payment record. Always permitted, so a mis-keyed settlement is correctable.",
                "operationId": "deleteApiIntercompanyTransactionsByIdPayment",
                "description": "Requires the `multi-company:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "multi-company:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/intercompany-transactions/eliminate-all": {
            "post": {
                "tags": [
                    "Multi-company & consolidation"
                ],
                "summary": "Eliminate every matched transaction for a period in one pass.",
                "operationId": "postApiIntercompanyTransactionsEliminateAll",
                "description": "Requires the `multi-company:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "multi-company:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "period": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "period": "2026-08"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/consolidation-groups": {
            "get": {
                "tags": [
                    "Multi-company & consolidation"
                ],
                "summary": "Consolidation groups and their members.",
                "operationId": "getApiConsolidationGroups",
                "description": "Requires the `multi-company:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "multi-company:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Multi-company & consolidation"
                ],
                "summary": "Define a consolidation group.",
                "operationId": "postApiConsolidationGroups",
                "description": "Requires the `multi-company:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "multi-company:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "name": "KMP Group"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/consolidation-groups/{id}": {
            "get": {
                "tags": [
                    "Multi-company & consolidation"
                ],
                "summary": "One group.",
                "operationId": "getApiConsolidationGroupsById",
                "description": "Requires the `multi-company:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "multi-company:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "delete": {
                "tags": [
                    "Multi-company & consolidation"
                ],
                "summary": "Delete a group.",
                "operationId": "deleteApiConsolidationGroupsById",
                "description": "Requires the `multi-company:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "multi-company:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/consolidation-groups/{id}/run": {
            "post": {
                "tags": [
                    "Multi-company & consolidation"
                ],
                "summary": "Run the consolidation and store the result.",
                "operationId": "postApiConsolidationGroupsByIdRun",
                "description": "Requires the `multi-company:write` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "multi-company:write"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "asAt": {
                                        "type": "string",
                                        "format": "date"
                                    }
                                }
                            },
                            "example": {
                                "asAt": "2027-02-28"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/consolidation-groups/{id}/entries": {
            "get": {
                "tags": [
                    "Multi-company & consolidation"
                ],
                "summary": "The consolidation entries produced by a run.",
                "operationId": "getApiConsolidationGroupsByIdEntries",
                "description": "Requires the `multi-company:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "multi-company:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/multi-company/overview": {
            "get": {
                "tags": [
                    "Multi-company & consolidation"
                ],
                "summary": "Per-entity dashboards — P&L summary, balance sheet and inter-company balances side by side.",
                "operationId": "getApiMultiCompanyOverview",
                "description": "Requires the `multi-company:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "multi-company:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/multi-company/ic-matrix": {
            "get": {
                "tags": [
                    "Multi-company & consolidation"
                ],
                "summary": "The cross-entity outstanding balance matrix.",
                "operationId": "getApiMultiCompanyIcMatrix",
                "description": "Requires the `multi-company:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "multi-company:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/multi-company/shared-contacts": {
            "get": {
                "tags": [
                    "Multi-company & consolidation"
                ],
                "summary": "Contacts shared across entities, with a per-entity badge.",
                "operationId": "getApiMultiCompanySharedContacts",
                "description": "Requires the `multi-company:read` scope when called with an API key. A first-party token is limited by the user's role instead.",
                "x-ledgr-scope": [
                    "multi-company:read"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/practitioner/clients": {
            "get": {
                "tags": [
                    "Accountant workspace"
                ],
                "summary": "The client console — every tenant this practitioner is linked to.",
                "operationId": "getApiPractitionerClients",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/practitioner/links": {
            "get": {
                "tags": [
                    "Accountant workspace"
                ],
                "summary": "Practitioner links and their state.",
                "operationId": "getApiPractitionerLinks",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Accountant workspace"
                ],
                "summary": "Invite a practitioner, or request access to a client.",
                "operationId": "postApiPractitionerLinks",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "email": {
                                        "type": "string"
                                    },
                                    "accessLevel": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "email": "accountant@example.co.za",
                                "accessLevel": "full"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/practitioner/links/{id}": {
            "delete": {
                "tags": [
                    "Accountant workspace"
                ],
                "summary": "End a link. Access stops immediately.",
                "operationId": "deleteApiPractitionerLinksById",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/practitioner/invitations": {
            "get": {
                "tags": [
                    "Accountant workspace"
                ],
                "summary": "Invitations waiting on this practitioner.",
                "operationId": "getApiPractitionerInvitations",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/practitioner/accept/{id}": {
            "post": {
                "tags": [
                    "Accountant workspace"
                ],
                "summary": "Accept an invitation.",
                "operationId": "postApiPractitionerAcceptById",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/practitioner/queries": {
            "get": {
                "tags": [
                    "Accountant workspace"
                ],
                "summary": "In-app queries between practitioner and client, in place of an email thread nobody can find later.",
                "operationId": "getApiPractitionerQueries",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Accountant workspace"
                ],
                "summary": "Raise a query, optionally against a specific transaction.",
                "operationId": "postApiPractitionerQueries",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "question": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "question": "Example"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/practitioner/queries/{id}/answer": {
            "post": {
                "tags": [
                    "Accountant workspace"
                ],
                "summary": "Answer a query.",
                "operationId": "postApiPractitionerQueriesByIdAnswer",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "answer": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "answer": "Example"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/practitioner/queries/{id}/resolve": {
            "post": {
                "tags": [
                    "Accountant workspace"
                ],
                "summary": "Close a query.",
                "operationId": "postApiPractitionerQueriesByIdResolve",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/practitioner/adjusting-journals": {
            "get": {
                "tags": [
                    "Accountant workspace"
                ],
                "summary": "Adjusting journals waiting on the client's review.",
                "operationId": "getApiPractitionerAdjustingJournals",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/migration/start": {
            "post": {
                "tags": [
                    "Migration & take-on"
                ],
                "summary": "Open a migration job and upload the source files.",
                "operationId": "postApiMigrationStart",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "sourceType": {
                                        "type": "string"
                                    },
                                    "csvContent": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "sourceType": "xero",
                                "csvContent": "name,email\\nAndy Cameron,andy@example.co.za"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/migration/jobs": {
            "get": {
                "tags": [
                    "Migration & take-on"
                ],
                "summary": "Migration jobs and their state.",
                "operationId": "getApiMigrationJobs",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/migration/jobs/{id}": {
            "get": {
                "tags": [
                    "Migration & take-on"
                ],
                "summary": "One job, with what parsed and what did not.",
                "operationId": "getApiMigrationJobsById",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "delete": {
                "tags": [
                    "Migration & take-on"
                ],
                "summary": "Discard a job.",
                "operationId": "deleteApiMigrationJobsById",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/migration/preview": {
            "post": {
                "tags": [
                    "Migration & take-on"
                ],
                "summary": "Parse and classify without writing. Same code path as apply, so the counts are the counts that will happen.",
                "operationId": "postApiMigrationPreview",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "csvContent": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "csvContent": "name,email\\nAndy Cameron,andy@example.co.za"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/migration/apply-contacts": {
            "post": {
                "tags": [
                    "Migration & take-on"
                ],
                "summary": "Write the parsed contacts. Duplicates are matched on email through the same contactMatchKey the CRM conversion uses.",
                "operationId": "postApiMigrationApplyContacts",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "csvContent": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "csvContent": "name,email\\nAndy Cameron,andy@example.co.za"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/migration/apply-accounts": {
            "post": {
                "tags": [
                    "Migration & take-on"
                ],
                "summary": "Write the parsed chart of accounts. Account-type aliases are normalised through AfsTypes so an imported Xero-shaped chart does not declare nil turnover on the ITR14.",
                "operationId": "postApiMigrationApplyAccounts",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "csvContent": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "csvContent": "name,email\\nAndy Cameron,andy@example.co.za"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/migration/apply-products": {
            "post": {
                "tags": [
                    "Migration & take-on"
                ],
                "summary": "Write the parsed products.",
                "operationId": "postApiMigrationApplyProducts",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "csvContent": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "csvContent": "name,email\\nAndy Cameron,andy@example.co.za"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/migration/apply-invoices": {
            "post": {
                "tags": [
                    "Migration & take-on"
                ],
                "summary": "Write the parsed documents. Existing document numbers are checked against every series so a number on a customer's pre-migration quote is never reissued.",
                "operationId": "postApiMigrationApplyInvoices",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "csvContent": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "csvContent": "name,email\\nAndy Cameron,andy@example.co.za"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/migration/apply-trial-balance": {
            "post": {
                "tags": [
                    "Migration & take-on"
                ],
                "summary": "Post the opening trial balance as a journal.",
                "operationId": "postApiMigrationApplyTrialBalance",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "csvContent": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "csvContent": "name,email\\nAndy Cameron,andy@example.co.za"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/sync/status": {
            "get": {
                "tags": [
                    "Offline sync"
                ],
                "summary": "The tenant's sync cursor and clock. Built, and instantiated only in tests.",
                "operationId": "getApiSyncStatus",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/sync/pull": {
            "get": {
                "tags": [
                    "Offline sync"
                ],
                "summary": "Changes since a cursor. Built and not wired — no client calls it.",
                "operationId": "getApiSyncPull",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/sync/batch": {
            "post": {
                "tags": [
                    "Offline sync"
                ],
                "summary": "Push a batch of local changes with their vector clocks. Built and not wired.",
                "operationId": "postApiSyncBatch",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "operations": {
                                        "type": "array",
                                        "items": {
                                            "type": "object",
                                            "properties": {
                                                "id": {
                                                    "type": "string"
                                                },
                                                "entityType": {
                                                    "type": "string"
                                                },
                                                "entityId": {
                                                    "type": "string"
                                                },
                                                "operation": {
                                                    "type": "string"
                                                },
                                                "payload": {
                                                    "type": "string"
                                                },
                                                "vectorClock": {
                                                    "type": "string"
                                                },
                                                "timestamp": {
                                                    "type": "integer"
                                                }
                                            }
                                        }
                                    },
                                    "nodeId": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "operations": [
                                    {
                                        "id": "<uuid>",
                                        "entityType": "",
                                        "entityId": "<uuid>",
                                        "operation": "Example",
                                        "payload": "Example",
                                        "vectorClock": "Example",
                                        "timestamp": 1
                                    }
                                ],
                                "nodeId": "<uuid>"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/ws/inventory": {
            "websocket": {
                "tags": [
                    "Offline sync"
                ],
                "summary": "Real-time stock updates. The socket exists and no client connects to it. Bearer JWT on the handshake, in its own authenticate block so the licence and API-key interceptors do not run on it.",
                "operationId": "websocketWsInventory",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/whatsapp/config": {
            "get": {
                "tags": [
                    "WhatsApp"
                ],
                "summary": "Whether WhatsApp is configured, and which templates are approved.",
                "operationId": "getApiWhatsappConfig",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "put": {
                "tags": [
                    "WhatsApp"
                ],
                "summary": "Set the tenant's WhatsApp configuration.",
                "operationId": "putApiWhatsappConfig",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "enabled": {
                                        "type": "boolean"
                                    },
                                    "senderDisplayName": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "enabled": true,
                                "senderDisplayName": "Your Business"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/whatsapp/messages": {
            "get": {
                "tags": [
                    "WhatsApp"
                ],
                "summary": "Messages sent and their delivery state.",
                "operationId": "getApiWhatsappMessages",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/whatsapp/messages/{id}": {
            "get": {
                "tags": [
                    "WhatsApp"
                ],
                "summary": "One message.",
                "operationId": "getApiWhatsappMessagesById",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/whatsapp/send": {
            "post": {
                "tags": [
                    "WhatsApp"
                ],
                "summary": "Send a message against an approved template.",
                "operationId": "postApiWhatsappSend",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "to": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "templateName": {
                                        "type": "string"
                                    },
                                    "templateParams": {
                                        "type": "object",
                                        "properties": {}
                                    }
                                }
                            },
                            "example": {
                                "to": "2026-09-30",
                                "templateName": "Example",
                                "templateParams": {}
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/api/whatsapp/test": {
            "post": {
                "tags": [
                    "WhatsApp"
                ],
                "summary": "Send a test message to prove the configuration, rather than discovering it at the first real send.",
                "operationId": "postApiWhatsappTest",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "to": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "templateName": {
                                        "type": "string"
                                    },
                                    "templateParams": {
                                        "type": "object",
                                        "properties": {}
                                    }
                                }
                            },
                            "example": {
                                "to": "2026-09-30",
                                "templateName": "Example",
                                "templateParams": {}
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/external/contacts/{contactId}/transactions": {
            "get": {
                "tags": [
                    "External ingest (API key)"
                ],
                "summary": "Billable events already posted for a contact. Scope transactions:read plus a key bound to that contact.",
                "operationId": "getExternalContactsByContactIdTransactions",
                "security": [
                    {
                        "apiKey": []
                    }
                ],
                "parameters": [
                    {
                        "name": "contactId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "External ingest (API key)"
                ],
                "summary": "Post a billable event. Scope transactions:write plus a bound contact, set when the key is generated under Roles → API keys.",
                "operationId": "postExternalContactsByContactIdTransactions",
                "security": [
                    {
                        "apiKey": []
                    }
                ],
                "parameters": [
                    {
                        "name": "contactId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "occurredAt": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "description": {
                                        "type": "string"
                                    },
                                    "quantity": {
                                        "type": "number"
                                    },
                                    "unitPrice": {
                                        "type": "number"
                                    },
                                    "currency": {
                                        "type": "string"
                                    },
                                    "vatRate": {
                                        "type": "number"
                                    },
                                    "externalRef": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "occurredAt": "2026-08-14",
                                "description": "1 200 API calls",
                                "quantity": 1200.0,
                                "unitPrice": 0.85,
                                "currency": "ZAR",
                                "vatRate": 15.0,
                                "externalRef": "usage-2026-08-14-a1"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/external/contacts/{contactId}/transactions/{id}": {
            "delete": {
                "tags": [
                    "External ingest (API key)"
                ],
                "summary": "Remove an event before it has been invoiced.",
                "operationId": "deleteExternalContactsByContactIdTransactionsById",
                "security": [
                    {
                        "apiKey": []
                    }
                ],
                "parameters": [
                    {
                        "name": "contactId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    },
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/admin/billing/run-monthly": {
            "post": {
                "tags": [
                    "External ingest (API key)"
                ],
                "summary": "Send ONE aggregate for a (contact, month) — the external app does its own per-transaction bookkeeping and reports the final monthly total here. Calling again UPDATES it in place (200) until it has been invoiced; after that it returns 409 and the invoice must be voided first.",
                "operationId": "postAdminBillingRunMonthly",
                "security": [
                    {
                        "apiKey": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "amount": {
                                        "type": "number"
                                    },
                                    "source": {
                                        "type": "string"
                                    },
                                    "month": {
                                        "type": "string"
                                    },
                                    "description": {
                                        "type": "string"
                                    },
                                    "currency": {
                                        "type": "string"
                                    },
                                    "vatRate": {
                                        "type": "number"
                                    }
                                }
                            },
                            "example": {
                                "amount": 1250.0,
                                "source": "app-x",
                                "month": "2026-08",
                                "description": "August usage total",
                                "currency": "ZAR",
                                "vatRate": 15.0
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "id": {
                                            "type": "string"
                                        },
                                        "contactId": {
                                            "type": "string"
                                        },
                                        "occurredAt": {
                                            "type": "string",
                                            "format": "date"
                                        },
                                        "amount": {
                                            "type": "number"
                                        },
                                        "externalRef": {
                                            "type": "string"
                                        },
                                        "status": {
                                            "type": "string"
                                        }
                                    }
                                },
                                "example": {
                                    "id": "<uuid>",
                                    "contactId": "<uuid>",
                                    "occurredAt": "2026-08-01",
                                    "amount": 1250.0,
                                    "externalRef": "monthly-aggregate:<contactId>:2026-08",
                                    "status": "pending"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/external/leads": {
            "post": {
                "tags": [
                    "External ingest (API key)"
                ],
                "summary": "Web-to-lead. Scope crm:write. Made idempotent by the caller's own external_ref under a unique index — a website form that retries on a dropped response must not create the lead twice, because two reps then phone the customer.",
                "operationId": "postExternalLeads",
                "security": [
                    {
                        "apiKey": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string"
                                    },
                                    "companyName": {
                                        "type": "string"
                                    },
                                    "email": {
                                        "type": "string"
                                    },
                                    "phone": {
                                        "type": "string"
                                    },
                                    "source": {
                                        "type": "string"
                                    },
                                    "notes": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "name": "Sipho Dlamini",
                                "companyName": "Dlamini Logistics",
                                "email": "sipho@dlamini.co.za",
                                "phone": "+27821234567",
                                "source": "website",
                                "notes": "Enquiry from the website contact form."
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/webhooks/payfast/itn/{tenantId}": {
            "post": {
                "tags": [
                    "Inbound webhooks"
                ],
                "summary": "PayFast's instant transaction notification. Signature-verified, confirmed with PayFast, then filed into the payments inbox. The merchant is resolved from the merchant_id PayFast echoes back, so a tenant may hold several.",
                "operationId": "postWebhooksPayfastItnByTenantId",
                "security": [],
                "parameters": [
                    {
                        "name": "tenantId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    }
                }
            }
        },
        "/webhooks/yoco/invoice/{tenantId}": {
            "post": {
                "tags": [
                    "Inbound webhooks"
                ],
                "summary": "Yoco payment events for a TENANT'S OWN invoices. Verified as Standard Webhooks (HMAC-SHA256 over webhook-id.webhook-timestamp.body) against a secret THAT TENANT stored, never a deployment one. A Yoco webhook names no merchant, so the tenant travels in this path — which is routing, not authorisation: the signature must match one of the tenant's own secrets and the invoice reference in the metadata must name the same tenant.",
                "operationId": "postWebhooksYocoInvoiceByTenantId",
                "security": [],
                "parameters": [
                    {
                        "name": "tenantId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    }
                }
            }
        },
        "/webhooks/yoco": {
            "post": {
                "tags": [
                    "Inbound webhooks"
                ],
                "summary": "Yoco payment events. HMAC-SHA256 over the raw body in X-Webhook-Signature, against LEDGR_WEBHOOK_SECRET_YOCO.",
                "operationId": "postWebhooksYoco",
                "security": [],
                "responses": {
                    "201": {
                        "description": "Success"
                    }
                }
            }
        },
        "/webhooks/payfast": {
            "post": {
                "tags": [
                    "Inbound webhooks"
                ],
                "summary": "The generic PayFast receiver, signed with LEDGR_WEBHOOK_SECRET_PAYFAST.",
                "operationId": "postWebhooksPayfast",
                "security": [],
                "responses": {
                    "201": {
                        "description": "Success"
                    }
                }
            }
        },
        "/webhooks/yoco/billing": {
            "post": {
                "tags": [
                    "Inbound webhooks"
                ],
                "summary": "Yoco events for LEDGR'S OWN licence checkouts — this is what mints and emails a key after a purchase.",
                "operationId": "postWebhooksYocoBilling",
                "security": [],
                "responses": {
                    "201": {
                        "description": "Success"
                    }
                }
            }
        },
        "/admin/login": {
            "post": {
                "tags": [
                    "Platform administration"
                ],
                "summary": "Issue an admin JWT. No prior auth.",
                "operationId": "postAdminLogin",
                "security": [],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "username": {
                                        "type": "string"
                                    },
                                    "password": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "username": "Example",
                                "password": "Example"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    }
                }
            }
        },
        "/admin": {
            "get": {
                "tags": [
                    "Platform administration"
                ],
                "summary": "The admin portal itself. HTML — its plan dropdown and blurbs are generated from Plan and modulesFor at compile time rather than being a fourth copy of the price list.",
                "operationId": "getAdmin",
                "security": [],
                "responses": {
                    "200": {
                        "description": "Success"
                    }
                }
            }
        },
        "/admin/licenses": {
            "get": {
                "tags": [
                    "Platform administration"
                ],
                "summary": "Issued licence keys.",
                "operationId": "getAdminLicenses",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/admin/licenses/generate": {
            "post": {
                "tags": [
                    "Platform administration"
                ],
                "summary": "Mint a key for 1, 3 or 12 months — or never expiring, which is a supported option for partners and pilots. The expiry is derived SERVER-side from termMonths, and seats come from Plan.maxUsers and are not editable.",
                "operationId": "postAdminLicensesGenerate",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "plan": {
                                        "type": "string"
                                    },
                                    "termMonths": {
                                        "type": "integer"
                                    }
                                }
                            },
                            "example": {
                                "plan": "business",
                                "termMonths": 12
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/admin/licenses/{code}": {
            "delete": {
                "tags": [
                    "Platform administration"
                ],
                "summary": "Revoke a key.",
                "operationId": "deleteAdminLicensesByCode",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "code",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/admin/orders": {
            "get": {
                "tags": [
                    "Platform administration"
                ],
                "summary": "Licence purchases across every tenant.",
                "operationId": "getAdminOrders",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/admin/orders/{id}/sync": {
            "post": {
                "tags": [
                    "Platform administration"
                ],
                "summary": "Re-poll Yoco for an order whose webhook never arrived.",
                "operationId": "postAdminOrdersByIdSync",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/admin/orders/{id}/resend": {
            "post": {
                "tags": [
                    "Platform administration"
                ],
                "summary": "Re-send the key email for an order.",
                "operationId": "postAdminOrdersByIdResend",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/admin/statutory/tax-years": {
            "get": {
                "tags": [
                    "Platform administration"
                ],
                "summary": "Published statutory overrides by year of assessment.",
                "operationId": "getAdminStatutoryTaxYears",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Platform administration"
                ],
                "summary": "Publish a tax year without a redeploy. Every row is re-validated ON LOAD, not just on publish, and each bracket's baseTax is recomputed from widths × rates. Resolution is override, then compiled built-in, then REFUSE — this is a way to give the engine a year before a release ships, not a way to make it guess.",
                "operationId": "postAdminStatutoryTaxYears",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "taxYearEndYear": {
                                        "type": "integer"
                                    },
                                    "brackets": {
                                        "type": "array",
                                        "items": {
                                            "type": "object",
                                            "properties": {
                                                "upTo": {
                                                    "type": "integer"
                                                },
                                                "rate": {
                                                    "type": "number"
                                                }
                                            }
                                        }
                                    },
                                    "primaryRebate": {
                                        "type": "number"
                                    },
                                    "thresholdUnder65": {
                                        "type": "number"
                                    }
                                }
                            },
                            "example": {
                                "taxYearEndYear": 2028,
                                "brackets": [
                                    {
                                        "upTo": 254000,
                                        "rate": 18.0
                                    }
                                ],
                                "primaryRebate": 17820.0,
                                "thresholdUnder65": 99000.0
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/admin/statutory/tax-years/{year}": {
            "get": {
                "tags": [
                    "Platform administration"
                ],
                "summary": "One year's brackets, rebates, thresholds, UIF ceiling, s11F cap and s6A credits.",
                "operationId": "getAdminStatutoryTaxYearsByYear",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "year",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "delete": {
                "tags": [
                    "Platform administration"
                ],
                "summary": "Withdraw a published year, falling back to the compiled table.",
                "operationId": "deleteAdminStatutoryTaxYearsByYear",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "year",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/admin/statutory/reload": {
            "post": {
                "tags": [
                    "Platform administration"
                ],
                "summary": "Reload the registry now. It refreshes on a revision token every LEDGR_STATUTORY_REFRESH_INTERVAL_SEC anyway — the tick interval is the whole cross-replica exposure window.",
                "operationId": "postAdminStatutoryReload",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/admin/statutory/annual-review": {
            "get": {
                "tags": [
                    "Platform administration"
                ],
                "summary": "The February review checklist — every figure that goes stale in a Budget, and where each is published.",
                "operationId": "getAdminStatutoryAnnualReview",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/admin/statutory/calendar": {
            "get": {
                "tags": [
                    "Platform administration"
                ],
                "summary": "The filing calendar as published — the dates a tax year is made of.",
                "operationId": "getAdminStatutoryCalendar",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/admin/statutory/calendar/preview": {
            "get": {
                "tags": [
                    "Platform administration"
                ],
                "summary": "What the calendar currently resolves to, and where each answer came from. Both halves in one response, because answering \"is our calendar right\" from two endpoints invites checking one of them.",
                "operationId": "getAdminStatutoryCalendarPreview",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/admin/statutory/calendar/days": {
            "post": {
                "tags": [
                    "Platform administration"
                ],
                "summary": "Publish a public holiday or non-business day.",
                "operationId": "postAdminStatutoryCalendarDays",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "kind": {
                                        "type": "string"
                                    },
                                    "date": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "name": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "kind": "public-holiday",
                                "date": "2027-03-21",
                                "name": "Human Rights Day"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/admin/statutory/calendar/days/{kind}/{date}": {
            "delete": {
                "tags": [
                    "Platform administration"
                ],
                "summary": "Withdraw a published day.",
                "operationId": "deleteAdminStatutoryCalendarDaysByKindByDate",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "kind",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "date",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/admin/statutory/calendar/deadlines": {
            "post": {
                "tags": [
                    "Platform administration"
                ],
                "summary": "Publish a filing season or deadline for a year.",
                "operationId": "postAdminStatutoryCalendarDeadlines",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "kind": {
                                        "type": "string"
                                    },
                                    "taxYearEndYear": {
                                        "type": "integer"
                                    },
                                    "dueDate": {
                                        "type": "string",
                                        "format": "date"
                                    }
                                }
                            },
                            "example": {
                                "kind": "itr12-season",
                                "taxYearEndYear": 2027,
                                "dueDate": "2026-10-23"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/admin/statutory/calendar/deadlines/{kind}/{year}": {
            "delete": {
                "tags": [
                    "Platform administration"
                ],
                "summary": "Withdraw a published deadline.",
                "operationId": "deleteAdminStatutoryCalendarDeadlinesByKindByYear",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "kind",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "year",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/admin/statutory/calendar/reload": {
            "post": {
                "tags": [
                    "Platform administration"
                ],
                "summary": "Reload the calendar now.",
                "operationId": "postAdminStatutoryCalendarReload",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/admin/pricing": {
            "get": {
                "tags": [
                    "Platform administration"
                ],
                "summary": "The price list as published, beside the compiled floor in Plans.kt that answers whenever no row is published.",
                "operationId": "getAdminPricing",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/admin/pricing/plans": {
            "post": {
                "tags": [
                    "Platform administration"
                ],
                "summary": "Publish a plan price and its storage allowance. Everything else a tier carries — its module set, seats, companies, API keys, invoice quota and both meter allowances — is published through /admin/plans (V308), which keeps its own trail so a price and a definition revert separately.",
                "operationId": "postAdminPricingPlans",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "planId": {
                                        "type": "string"
                                    },
                                    "priceMonthlyCents": {
                                        "type": "integer"
                                    }
                                }
                            },
                            "example": {
                                "planId": "business",
                                "priceMonthlyCents": 49900
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/admin/pricing/plans/{planId}": {
            "delete": {
                "tags": [
                    "Platform administration"
                ],
                "summary": "Withdraw a published price; the compiled figure stands again.",
                "operationId": "deleteAdminPricingPlansByPlanId",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "planId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/admin/pricing/settings": {
            "post": {
                "tags": [
                    "Platform administration"
                ],
                "summary": "Publish a storage or payroll uplift.",
                "operationId": "postAdminPricingSettings",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "key": {
                                        "type": "string"
                                    },
                                    "amountCents": {
                                        "type": "integer"
                                    }
                                }
                            },
                            "example": {
                                "key": "storage.overage.per.gb",
                                "amountCents": 24900
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/admin/pricing/settings/{key}": {
            "delete": {
                "tags": [
                    "Platform administration"
                ],
                "summary": "Withdraw a published setting.",
                "operationId": "deleteAdminPricingSettingsByKey",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "key",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/admin/pricing/reload": {
            "post": {
                "tags": [
                    "Platform administration"
                ],
                "summary": "Reload the price list now.",
                "operationId": "postAdminPricingReload",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/admin/pricing/notices": {
            "get": {
                "tags": [
                    "Platform administration"
                ],
                "summary": "Price-change notices, drafted, released or cancelled.",
                "operationId": "getAdminPricingNotices",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Platform administration"
                ],
                "summary": "Compose the notice that tells customers about a price change.",
                "operationId": "postAdminPricingNotices",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "effectiveFrom": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "subject": {
                                        "type": "string"
                                    },
                                    "body": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "effectiveFrom": "2026-11-01",
                                "subject": "A change to your Ledgr plan price",
                                "body": "…"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/admin/pricing/notices/{id}/release": {
            "post": {
                "tags": [
                    "Platform administration"
                ],
                "summary": "Release a notice to affected tenants.",
                "operationId": "postAdminPricingNoticesByIdRelease",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/admin/pricing/notices/{id}/cancel": {
            "post": {
                "tags": [
                    "Platform administration"
                ],
                "summary": "Cancel an unreleased notice.",
                "operationId": "postAdminPricingNoticesByIdCancel",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/admin/plans": {
            "get": {
                "tags": [
                    "Platform administration"
                ],
                "summary": "Every plan's definition as published, beside the compiled floor in Plans.kt that answers whenever no row is published — plus the module catalogue the checkboxes are drawn from and the live tenant count per tier.",
                "operationId": "getAdminPlans",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Platform administration"
                ],
                "summary": "Publish what a plan includes. 422 with reasons when the definition cannot be right — an unknown module key, or a set without 'settings', which carries the billing screen. 409 with the impact when it REMOVES a module and acknowledgeRemovals is not set: removal is immediate and retrospective, so every tenant on the tier answers 403 on the next request.",
                "operationId": "postAdminPlans",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "planId": {
                                        "type": "string"
                                    },
                                    "modules": {
                                        "type": "array",
                                        "items": {
                                            "type": "string"
                                        }
                                    },
                                    "maxUsers": {
                                        "type": "integer"
                                    },
                                    "maxCompanies": {
                                        "type": "integer"
                                    },
                                    "maxApiKeys": {
                                        "type": "integer"
                                    },
                                    "monthlyInvoiceLimit": {
                                        "type": "null"
                                    },
                                    "includedWhatsAppConversations": {
                                        "type": "integer"
                                    },
                                    "includedAiScans": {
                                        "type": "integer"
                                    },
                                    "source": {
                                        "type": "string"
                                    },
                                    "effectiveFrom": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "acknowledgeRemovals": {
                                        "type": "boolean"
                                    }
                                }
                            },
                            "example": {
                                "planId": "business",
                                "modules": [
                                    "dashboard",
                                    "invoices",
                                    "settings"
                                ],
                                "maxUsers": 10,
                                "maxCompanies": 1,
                                "maxApiKeys": 0,
                                "monthlyInvoiceLimit": null,
                                "includedWhatsAppConversations": 250,
                                "includedAiScans": 200,
                                "source": "PRICE-11 — Reports moves down to Starter",
                                "effectiveFrom": "2026-09-01",
                                "acknowledgeRemovals": false
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/admin/plans/preview": {
            "post": {
                "tags": [
                    "Platform administration"
                ],
                "summary": "What publishing this definition WOULD change, writing nothing: the modules added and removed, the live tenants on the plan, and any reason it could not be published. Shares one impact calculation with the publish path, so the numbers shown are the numbers the write acts on.",
                "operationId": "postAdminPlansPreview",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "planId": {
                                        "type": "string"
                                    },
                                    "modules": {
                                        "type": "array",
                                        "items": {
                                            "type": "string"
                                        }
                                    },
                                    "maxUsers": {
                                        "type": "integer"
                                    },
                                    "maxCompanies": {
                                        "type": "integer"
                                    },
                                    "maxApiKeys": {
                                        "type": "integer"
                                    },
                                    "monthlyInvoiceLimit": {
                                        "type": "null"
                                    },
                                    "includedWhatsAppConversations": {
                                        "type": "integer"
                                    },
                                    "includedAiScans": {
                                        "type": "integer"
                                    },
                                    "source": {
                                        "type": "string"
                                    },
                                    "effectiveFrom": {
                                        "type": "string",
                                        "format": "date"
                                    }
                                }
                            },
                            "example": {
                                "planId": "business",
                                "modules": [
                                    "dashboard",
                                    "invoices",
                                    "settings"
                                ],
                                "maxUsers": 10,
                                "maxCompanies": 1,
                                "maxApiKeys": 0,
                                "monthlyInvoiceLimit": null,
                                "includedWhatsAppConversations": 250,
                                "includedAiScans": 200,
                                "source": "Why this tier includes what it includes",
                                "effectiveFrom": "2026-09-01"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/admin/plans/{planId}": {
            "delete": {
                "tags": [
                    "Platform administration"
                ],
                "summary": "Withdraw a published definition; the tier compiled into the build stands again. Not gated on an acknowledgement even though it can remove a module — getting back to the build must never be the harder path.",
                "operationId": "deleteAdminPlansByPlanId",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "planId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/admin/plans/{planId}/reapply-seats": {
            "post": {
                "tags": [
                    "Platform administration"
                ],
                "summary": "Copy the plan's seat allowance onto every live tenant on it, and report how many were updated. Its own action rather than a side effect of publishing: a seat count is copied onto the tenant when a licence is issued or renewed, so a published figure otherwise reaches them at their next renewal. Nobody loses access — the cap is enforced when a user is invited.",
                "operationId": "postAdminPlansByPlanIdReapplySeats",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "planId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/admin/plans/reload": {
            "post": {
                "tags": [
                    "Platform administration"
                ],
                "summary": "Re-read the published definitions into THIS instance. The scheduler and the plan-config-refresh job do it on a revision token; this is the manual nudge.",
                "operationId": "postAdminPlansReload",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/admin/statutory/vat-rates": {
            "get": {
                "tags": [
                    "Platform administration"
                ],
                "summary": "The VAT schedule as it will actually be applied, plus today's answer. Compiled and published rows in ONE list rather than two — the question is \"what will we charge\", and a screen showing published rows alone cannot answer it.",
                "operationId": "getAdminStatutoryVatRates",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "post": {
                "tags": [
                    "Platform administration"
                ],
                "summary": "Publish a rate from a date. A wrong figure here charges the wrong tax to every customer of every tenant, on documents that look entirely normal — so a refusal comes back 422 naming each reason, never a bare 400. Changing the rate needs no migration and no backfill: every document stores the rate it was raised at.",
                "operationId": "postAdminStatutoryVatRates",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "effectiveFrom": {
                                        "type": "string",
                                        "format": "date"
                                    },
                                    "ratePercent": {
                                        "type": "number"
                                    },
                                    "notes": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "effectiveFrom": "2027-05-01",
                                "ratePercent": 15.5,
                                "notes": "Budget 2027"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/admin/statutory/vat-rates/on/{date}": {
            "get": {
                "tags": [
                    "Platform administration"
                ],
                "summary": "The rate on a given date, which is the question a support query actually asks. {date} is an ISO YYYY-MM-DD and is the identity of a rate — there is no surrogate id, because what an operator reasons about is \"the rate from 1 May\".",
                "operationId": "getAdminStatutoryVatRatesOnByDate",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "date",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/admin/statutory/vat-rates/{date}": {
            "delete": {
                "tags": [
                    "Platform administration"
                ],
                "summary": "Take a published rate out of service. The compiled schedule answers again.",
                "operationId": "deleteAdminStatutoryVatRatesByDate",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "date",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/admin/statutory/vat-rates/reload": {
            "post": {
                "tags": [
                    "Platform administration"
                ],
                "summary": "Re-read the table on THIS instance. Not how a publish propagates — the scheduler converges every minute — but how an operator confirms what the instance answering them currently holds.",
                "operationId": "postAdminStatutoryVatRatesReload",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/admin/billing/aggregate-monthly": {
            "post": {
                "tags": [
                    "Platform administration"
                ],
                "summary": "Roll every pending billable event from the prior month into draft invoices, grouped by tenant × contact. Idempotent, and normally driven by Cloud Scheduler on the 1st.",
                "operationId": "postAdminBillingAggregateMonthly",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "tenantsProcessed": {
                                            "type": "integer"
                                        },
                                        "invoicesCreated": {
                                            "type": "integer"
                                        },
                                        "eventsInvoiced": {
                                            "type": "integer"
                                        },
                                        "periodFrom": {
                                            "type": "string",
                                            "format": "date"
                                        },
                                        "periodTo": {
                                            "type": "string",
                                            "format": "date"
                                        }
                                    }
                                },
                                "example": {
                                    "tenantsProcessed": 12,
                                    "invoicesCreated": 47,
                                    "eventsInvoiced": 312,
                                    "periodFrom": "2026-07-01",
                                    "periodTo": "2026-07-31"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/admin/tenants": {
            "get": {
                "tags": [
                    "Platform administration"
                ],
                "summary": "Every account, newest first, capped at 500 — with the one figure a customer quotes back on the telephone: what the next renewal costs today, add-ons and payroll banding included. `?q=` searches name, slug and owner email.",
                "operationId": "getAdminTenants",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/admin/tenants/{id}": {
            "get": {
                "tags": [
                    "Platform administration"
                ],
                "summary": "One account with everything support can change: plan, seats, add-ons held, the users and their roles, and the licence's own validity.",
                "operationId": "getAdminTenantsById",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            },
            "patch": {
                "tags": [
                    "Platform administration"
                ],
                "summary": "Fix the account's own fields. A slug that would collide with another tenant is refused plainly rather than surfacing as a 500 — the constraint would otherwise take down the account it clashed with.",
                "operationId": "patchAdminTenantsById",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string"
                                    },
                                    "slug": {
                                        "type": "string"
                                    },
                                    "licenseValidUntil": {
                                        "type": "string",
                                        "format": "date"
                                    }
                                }
                            },
                            "example": {
                                "name": "Nkosi Tech (Pty) Ltd",
                                "slug": "nkosi-tech",
                                "licenseValidUntil": "2027-03-31"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/admin/tenants/{id}/users/{userId}": {
            "patch": {
                "tags": [
                    "Platform administration"
                ],
                "summary": "Correct a colleague's name, email, role or active flag from the back office. A duplicate email inside the tenant is refused for the same reason a duplicate slug is.",
                "operationId": "patchAdminTenantsByIdUsersByUserId",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    },
                    {
                        "name": "userId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "email": {
                                        "type": "string"
                                    },
                                    "role": {
                                        "type": "string"
                                    },
                                    "isActive": {
                                        "type": "boolean"
                                    }
                                }
                            },
                            "example": {
                                "email": "thandi@nkositech.co.za",
                                "role": "accountant",
                                "isActive": true
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/admin/tenants/{id}/transfer-ownership": {
            "post": {
                "tags": [
                    "Platform administration"
                ],
                "summary": "Move the owner seat on the customer's behalf. The tenant-facing route lets the owner do this themselves; a customer whose owner has already LEFT cannot, which is the case this exists for. Same invariant, enforced the same way: one transaction demotes every current owner/admin and promotes the target, so the account never has none or two.",
                "operationId": "postAdminTenantsByIdTransferOwnership",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "userId": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "userId": "<uuid>"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/admin/tenants/{id}/plan": {
            "post": {
                "tags": [
                    "Platform administration"
                ],
                "summary": "Upgrade or downgrade an account. Seats follow the tier rather than being typed — a plan and a seat cap that disagree is invisible until somebody cannot invite a colleague. Runs `pruneAddonsForPlan` in the SAME transaction, so an add-on the new tier already includes (or one below its floor) is cancelled at once and the bill cannot disagree with what the tenant can reach; the renewal schedule is moved to the new tier too.",
                "operationId": "postAdminTenantsByIdPlan",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "plan": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "plan": "professional"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/admin/tenants/{id}/addons": {
            "post": {
                "tags": [
                    "Platform administration"
                ],
                "summary": "Add an add-on or set its quantity. Refused where the plan already includes it or does not reach its floor, and clamped to the add-on's own maximum. Quantity must be at least 1 — removing one is a DELETE, because a zero-quantity holding grants nothing and still has a row somebody could bill.",
                "operationId": "postAdminTenantsByIdAddons",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "addonId": {
                                        "type": "string"
                                    },
                                    "quantity": {
                                        "type": "integer"
                                    },
                                    "notes": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "addonId": "seat-pack-5",
                                "quantity": 2,
                                "notes": "Agreed on the call"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/admin/tenants/{id}/addons/{addonId}": {
            "delete": {
                "tags": [
                    "Platform administration"
                ],
                "summary": "Cancel an add-on. Sets `cancelled_at` rather than deleting the row: an add-on held for four months of a twelve-month term is part of explaining that term's invoice.",
                "operationId": "deleteAdminTenantsByIdAddonsByAddonId",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    },
                    {
                        "name": "addonId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/health": {
            "get": {
                "tags": [
                    "Operations & discovery"
                ],
                "summary": "Liveness. Deliberately ignores the database — a liveness probe that fails during a Cloud SQL blip would kill every instance of a revision that was fine.",
                "operationId": "getHealth",
                "security": [],
                "responses": {
                    "200": {
                        "description": "Success",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "status": {
                                            "type": "string"
                                        },
                                        "service": {
                                            "type": "string"
                                        }
                                    }
                                },
                                "example": {
                                    "status": "ok",
                                    "service": "ledgr-api"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/readyz": {
            "get": {
                "tags": [
                    "Operations & discovery"
                ],
                "summary": "Readiness, and the service's startup probe: 200 only when the startup database init reported ok AND a live SELECT 1 succeeds. This is what stops a container that boots with a broken migration from being promoted to 100% of traffic.",
                "operationId": "getReadyz",
                "security": [],
                "responses": {
                    "200": {
                        "description": "Success"
                    }
                }
            }
        },
        "/diagnostics": {
            "get": {
                "tags": [
                    "Operations & discovery"
                ],
                "summary": "Triage for \"the server is up but nothing works\": startup DB status, a live probe, the highest applied migration ordered NUMERICALLY, failed migrations, the environment, and whether email and WhatsApp are configured. Coarse statuses only — it is unauthenticated, so it must never leak internal error strings.",
                "operationId": "getDiagnostics",
                "security": [],
                "responses": {
                    "200": {
                        "description": "Success",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "service": {
                                            "type": "string"
                                        },
                                        "status": {
                                            "type": "string"
                                        },
                                        "startup_db_init": {
                                            "type": "string"
                                        },
                                        "live_db_check": {
                                            "type": "string"
                                        },
                                        "schema_version": {
                                            "type": "string"
                                        },
                                        "failed_migrations": {
                                            "type": "integer"
                                        },
                                        "migrations_enforced": {
                                            "type": "boolean"
                                        },
                                        "environment": {
                                            "type": "string"
                                        },
                                        "email_configured": {
                                            "type": "boolean"
                                        },
                                        "whatsapp_configured": {
                                            "type": "boolean"
                                        }
                                    }
                                },
                                "example": {
                                    "service": "ledgr-api",
                                    "status": "ok",
                                    "startup_db_init": "ok",
                                    "live_db_check": "ok",
                                    "schema_version": "268",
                                    "failed_migrations": 0,
                                    "migrations_enforced": true,
                                    "environment": "production",
                                    "email_configured": true,
                                    "whatsapp_configured": false
                                }
                            }
                        }
                    }
                }
            }
        },
        "/openapi.json": {
            "get": {
                "tags": [
                    "Operations & discovery"
                ],
                "summary": "The OpenAPI 3.1 document, at the conventional root path tools probe for. Generated from this same catalogue, so the two can never disagree.",
                "operationId": "getOpenapi.json",
                "security": [],
                "responses": {
                    "200": {
                        "description": "Success"
                    }
                }
            }
        },
        "/api-docs/openapi.json": {
            "get": {
                "tags": [
                    "Operations & discovery"
                ],
                "summary": "The same document, on a path an integrator can be given without it looking like an internal admin URL.",
                "operationId": "getApiDocsOpenapi.json",
                "security": [],
                "responses": {
                    "200": {
                        "description": "Success"
                    }
                }
            }
        },
        "/developers/playground/spec.json": {
            "get": {
                "tags": [
                    "Operations & discovery"
                ],
                "summary": "This catalogue, as JSON, with each endpoint's module, plan and scope resolved. What the playground renders.",
                "operationId": "getDevelopersPlaygroundSpec.json",
                "security": [],
                "responses": {
                    "200": {
                        "description": "Success"
                    }
                }
            }
        },
        "/admin/api-docs": {
            "get": {
                "tags": [
                    "Operations & discovery"
                ],
                "summary": "Permanent redirect to /developers/playground/. The console moved out of /admin, and these URLs are in sent emails and whatever Google has already indexed.",
                "operationId": "getAdminApiDocs",
                "security": [],
                "responses": {
                    "200": {
                        "description": "Success"
                    }
                }
            }
        },
        "/admin/api-docs/": {
            "get": {
                "tags": [
                    "Operations & discovery"
                ],
                "summary": "The same redirect, with the trailing slash.",
                "operationId": "getAdminApiDocsSlash",
                "security": [],
                "responses": {
                    "200": {
                        "description": "Success"
                    }
                }
            }
        },
        "/admin/api-docs/spec.json": {
            "get": {
                "tags": [
                    "Operations & discovery"
                ],
                "summary": "Permanent redirect to /developers/playground/spec.json.",
                "operationId": "getAdminApiDocsSpec.json",
                "security": [],
                "responses": {
                    "200": {
                        "description": "Success"
                    }
                }
            }
        },
        "/docs/search.json": {
            "get": {
                "tags": [
                    "Operations & discovery"
                ],
                "summary": "The documentation search index, shipped as data for the browser to filter rather than as a search endpoint — the whole corpus is about 60 KB, so a request per keystroke would cost more than sending it once, and it keeps working on a flaky connection.",
                "operationId": "getDocsSearch.json",
                "security": [],
                "responses": {
                    "200": {
                        "description": "Success"
                    }
                }
            }
        },
        "/internal/jobs/run": {
            "post": {
                "tags": [
                    "Operations & discovery"
                ],
                "summary": "Run named background jobs. Verifies Cloud Scheduler's OIDC token itself, because the service must stay public for the website and the API.",
                "operationId": "postInternalJobsRun",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "jobs": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": {
                                "jobs": "statutory-refresh,license-renewals,tenant-retention,crm-tasks,attachment-backfill"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        },
        "/internal/reminders/run": {
            "post": {
                "tags": [
                    "Operations & discovery"
                ],
                "summary": "Run the invoice and bill reminder sweep. Dedupe is a conditional UPDATE on the row itself, not a lease table.",
                "operationId": "postInternalRemindersRun",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Success"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    }
                }
            }
        }
    }
}