{"openapi":"3.0.3","info":{"title":"accounting API","description":"Public REST API for managing clients, invoices, and recurring forecasts. Authenticated with Sanctum personal access tokens, scoped to a single team.","version":"1.0.0"},"servers":[{"url":"https://vorgio.app"}],"tags":[{"name":"Checkout intents","description":"\nTwo-step checkout for the JS widget (sub-plan 11 §6):\n\n  1. Shop's server: `POST /v1/checkout-intents` (Bearer token,\n     `checkouts:write`). Stores the payload, returns id + one-shot\n     `client_secret`.\n  2. Browser (`vorgio.js`): `POST /v1/checkout-intents/{id}/confirm` with\n     the `client_secret`. No Sanctum auth — the secret IS the credential.\n     The same {@see CreateCheckout} composition that backs `/v1/checkouts`\n     runs, so the contract surface for shop integrators stays one shape."},{"name":"Checkouts","description":"\nHigh-level integration endpoint for third-party shops. One call creates or\nupdates the customer (`client.external_id` keyed per team), issues a\ngapless-numbered invoice, and queues the invoice email. The shop receives\n`invoice.sent` and `invoice.paid` webhook events for fulfilment."},{"name":"Clients","description":"\nManage the clients (customers) inside your team."},{"name":"Invoices","description":"\nCreate, send, and manage invoices and offers."},{"name":"Recurring","description":"\nForecasts of upcoming recurring-invoice generations.\n\nThe full forecast computation lands in sub-plan 07; this controller exposes\nwhatever is already on `invoices.next_invoice_at` so consumers can integrate\nagainst the route contract today."}],"components":{"securitySchemes":{"default":{"type":"http","scheme":"bearer","description":"Tokens are minted from the team settings page. Each token carries a list of scoped abilities (`clients:read`, `invoices:write`, …) and is bound to a single team — it cannot access data in other teams."}}},"security":[{"default":[]}],"paths":{"/api/v1/checkout-intents":{"post":{"summary":"Create a checkout intent.","operationId":"createACheckoutIntent","description":"Server-to-server. Validates the same body shape as `POST /v1/checkouts`\n(plus an optional `return_url`) and stores it for the browser to\nconfirm.","parameters":[{"in":"header","name":"Idempotency-Key","description":"","example":"Required.","schema":{"type":"string"}}],"responses":[],"tags":["Checkout intents"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"client":{"type":"object","description":"","example":[],"properties":{"name":{"type":"string","description":"value darf nicht länger als 255 Zeichen sein.","example":"z"},"name_addition":{"type":"string","description":"value darf nicht länger als 255 Zeichen sein.","example":"m","nullable":true},"address":{"type":"string","description":"value darf nicht länger als 255 Zeichen sein.","example":"i"},"address_addition":{"type":"string","description":"value darf nicht länger als 255 Zeichen sein.","example":"y","nullable":true},"zip":{"type":"string","description":"value darf nicht länger als 20 Zeichen sein.","example":"vdljnikhwaykcmyu"},"city":{"type":"string","description":"value darf nicht länger als 255 Zeichen sein.","example":"w"},"country":{"type":"string","description":"value muss 2 Zeichen lang sein.","example":"pw"},"email":{"type":"string","description":"value muss eine gültige E-Mail-Adresse sein. value darf nicht länger als 255 Zeichen sein.","example":"emelie.baumbach@example.net","nullable":true},"email_cc":{"type":"array","description":"value muss eine gültige E-Mail-Adresse sein. value darf nicht länger als 255 Zeichen sein.","example":["s"],"items":{"type":"string"}},"email_text":{"type":"string","description":"","example":"architecto","nullable":true},"language":{"type":"string","description":"","example":"en","enum":["de","en"]},"rate":{"type":"integer","description":"value muss mindestens 0 sein.","example":39},"vat":{"type":"number","description":"value muss mindestens 0 sein. value darf nicht größer als 100 sein.","example":7},"default_position_mode":{"type":"string","description":"","example":"hourly","enum":["hourly","fixed"]},"invoice_note":{"type":"string","description":"","example":"architecto","nullable":true},"iban":{"type":"string","description":"value darf nicht länger als 34 Zeichen sein.","example":"n","nullable":true},"bic":{"type":"string","description":"value darf nicht länger als 11 Zeichen sein.","example":"gzmiyv","nullable":true},"vat_id":{"type":"string","description":"value darf nicht länger als 64 Zeichen sein.","example":"d","nullable":true},"buyer_reference":{"type":"string","description":"value darf nicht länger als 255 Zeichen sein.","example":"l","nullable":true},"e_invoice_format":{"type":"string","description":"","example":"none","enum":["none","zugferd","xrechnung"]},"external_id":{"type":"string","description":"value darf nicht länger als 255 Zeichen sein.","example":"j","nullable":true}},"required":["name","address","zip","city","country","language","rate","vat","default_position_mode"]},"invoice":{"type":"object","description":"","example":[],"properties":{"positions":{"type":"array","description":"value muss mindestens 1 Elemente enthalten.","example":[[]],"items":{"type":"object","properties":{"id":{"type":"string","description":"value muss eine gültige UUID sein.","example":"977e5426-8d13-3824-86aa-b092f8ae52c5"},"date":{"type":"string","description":"value ist kein gültiges Datum.","example":"2026-05-18T10:08:43"},"mode":{"type":"string","description":"","example":"fixed","enum":["hourly","fixed"]},"description":{"type":"string","description":"value darf nicht länger als 1000 Zeichen sein.","example":"Fugiat sunt nihil accusantium harum mollitia."},"hours":{"type":"number","description":"","example":4326.41688,"nullable":true},"amount_cents":{"type":"integer","description":"","example":16,"nullable":true}},"required":["id","date","mode","description"]}},"tax_rate":{"type":"number","description":"value muss mindestens 0 sein. value darf nicht größer als 100 sein.","example":17},"billing_date":{"type":"string","description":"value ist kein gültiges Datum.","example":"2026-05-18T10:08:43","nullable":true},"billing_period":{"type":"string","description":"value muss 7 Zeichen lang sein.","example":"ikhwayk","nullable":true},"due_offset_days":{"type":"integer","description":"value muss mindestens 0 sein. value darf nicht größer als 255 sein.","example":8},"due_at":{"type":"string","description":"value ist kein gültiges Datum.","example":"2026-05-18T10:08:43","nullable":true},"subject":{"type":"string","description":"value darf nicht länger als 255 Zeichen sein.","example":"m","nullable":true},"salutation":{"type":"string","description":"value darf nicht länger als 255 Zeichen sein.","example":"y","nullable":true},"description":{"type":"string","description":"","example":"Eius et animi quos velit et.","nullable":true},"note":{"type":"string","description":"","example":"architecto","nullable":true},"every":{"type":"string","description":"","example":"monthly","enum":["daily","weekly","biweekly","monthly","bimonthly","quarterly","semiannual","yearly"],"nullable":true},"next_invoice_at":{"type":"string","description":"This field is required when <code>invoice.every</code> is present. value ist kein gültiges Datum.","example":"2026-05-18T10:08:43","nullable":true}},"required":["positions","tax_rate","due_offset_days"]},"send":{"type":"object","description":"","example":null,"properties":{"subject":{"type":"string","description":"value darf nicht länger als 255 Zeichen sein.","example":"b","nullable":true},"body":{"type":"string","description":"value darf nicht länger als 50000 Zeichen sein.","example":"n","nullable":true},"cc":{"type":"array","description":"value muss eine gültige E-Mail-Adresse sein. value darf nicht länger als 255 Zeichen sein.","example":["g"],"items":{"type":"string"}}}},"metadata":{"type":"object","description":"","example":null,"properties":[],"nullable":true},"return_url":{"type":"string","description":"Must be a valid URL. value darf nicht länger als 2048 Zeichen sein.","example":"http://bailey.com/","nullable":true}},"required":["client","invoice"]}}}}}},"/api/v1/checkout-intents/{id}/confirm":{"post":{"summary":"Confirm a checkout intent.","operationId":"confirmACheckoutIntent","description":"Public endpoint — authenticated only by the `client_secret` body\nfield. One-shot: a second confirm returns 409. Composes the same\nclient + invoice + send flow as `/v1/checkouts`.","parameters":[],"responses":[],"tags":["Checkout intents"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"client_secret":{"type":"string","description":"value darf nicht länger als 128 Zeichen sein.","example":"b"}},"required":["client_secret"]}}}}},"parameters":[{"in":"path","name":"id","description":"The ID of the checkout intent.","example":"architecto","required":true,"schema":{"type":"string"}}]},"/api/v1/checkouts":{"post":{"summary":"Create a checkout","operationId":"createACheckout","description":"Wraps client find-or-create + invoice creation + invoice email send\ninto one idempotent call.","parameters":[{"in":"header","name":"Idempotency-Key","description":"","example":"Required.","schema":{"type":"string"}}],"responses":[],"tags":["Checkouts"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"client":{"type":"object","description":"Client (customer) details. Looked up\n  by `external_id` within the current team; created if not found,\n  updated in place if found.","example":[],"properties":{"name":{"type":"string","description":"","example":"architecto"},"name_addition":{"type":"string","description":"value darf nicht länger als 255 Zeichen sein.","example":"m","nullable":true},"address":{"type":"string","description":"","example":"architecto"},"address_addition":{"type":"string","description":"value darf nicht länger als 255 Zeichen sein.","example":"y","nullable":true},"zip":{"type":"string","description":"","example":"architecto"},"city":{"type":"string","description":"","example":"architecto"},"country":{"type":"string","description":"ISO 3166-1 alpha-2.","example":"architecto"},"email":{"type":"string","description":"Required to send the invoice email.","example":"gbailey@example.net","nullable":true},"email_cc":{"type":"array","description":"value muss eine gültige E-Mail-Adresse sein. value darf nicht länger als 255 Zeichen sein.","example":["s"],"items":{"type":"string"}},"email_text":{"type":"string","description":"","example":"architecto","nullable":true},"language":{"type":"string","description":"","example":"en","enum":["de","en"]},"rate":{"type":"integer","description":"Hourly rate in cents (0 if N/A).","example":16},"vat":{"type":"number","description":"","example":4326.41688},"default_position_mode":{"type":"string","description":"","example":"fixed","enum":["hourly","fixed"]},"invoice_note":{"type":"string","description":"","example":"architecto","nullable":true},"iban":{"type":"string","description":"value darf nicht länger als 34 Zeichen sein.","example":"n","nullable":true},"bic":{"type":"string","description":"value darf nicht länger als 11 Zeichen sein.","example":"gzmiyv","nullable":true},"vat_id":{"type":"string","description":"value darf nicht länger als 64 Zeichen sein.","example":"d","nullable":true},"buyer_reference":{"type":"string","description":"value darf nicht länger als 255 Zeichen sein.","example":"l","nullable":true},"e_invoice_format":{"type":"string","description":"","example":"xrechnung","enum":["none","zugferd","xrechnung"]},"external_id":{"type":"string","description":"Stable identifier from the shop\n  (e.g. `wc_customer_42`). Strongly recommended — without it every\n  checkout creates a new client.","example":"architecto","nullable":true}},"required":["name","address","zip","city","country","language","rate","vat","default_position_mode"]},"invoice":{"type":"object","description":"","example":[],"properties":{"positions":{"type":"array","description":"At least one row.","example":[[]],"items":{"type":"object","properties":{"id":{"type":"string","description":"UUID.","example":"architecto"},"date":{"type":"date","description":"","example":"architecto"},"mode":{"type":"string","description":"","example":"hourly","enum":["hourly","fixed"]},"description":{"type":"string","description":"","example":"Eius et animi quos velit et."},"hours":{"type":"number","description":"Required when mode=hourly.","example":4326.41688,"nullable":true},"amount_cents":{"type":"integer","description":"Required when mode=fixed.","example":16,"nullable":true}},"required":["id","date","mode","description"]}},"tax_rate":{"type":"number","description":"VAT % (0–100).","example":4326.41688},"billing_date":{"type":"string","description":"value ist kein gültiges Datum.","example":"2026-05-18T10:08:43","nullable":true},"billing_period":{"type":"string","description":"value muss 7 Zeichen lang sein.","example":"ikhwayk","nullable":true},"due_offset_days":{"type":"integer","description":"","example":16},"due_at":{"type":"string","description":"value ist kein gültiges Datum.","example":"2026-05-18T10:08:43","nullable":true},"subject":{"type":"string","description":"","example":"architecto","nullable":true},"salutation":{"type":"string","description":"value darf nicht länger als 255 Zeichen sein.","example":"y","nullable":true},"description":{"type":"string","description":"","example":"Eius et animi quos velit et.","nullable":true},"note":{"type":"string","description":"","example":"architecto","nullable":true},"every":{"type":"string","description":"","example":"quarterly","enum":["daily","weekly","biweekly","monthly","bimonthly","quarterly","semiannual","yearly"],"nullable":true},"next_invoice_at":{"type":"string","description":"This field is required when <code>invoice.every</code> is present. value ist kein gültiges Datum.","example":"2026-05-18T10:08:43","nullable":true}},"required":["positions","tax_rate","due_offset_days"]},"send":{"type":"object","description":"","example":[],"properties":{"subject":{"type":"string","description":"Mail subject.","example":"architecto","nullable":true},"body":{"type":"string","description":"Mail body (template vars allowed).","example":"architecto","nullable":true},"cc":{"type":"array","description":"Optional CC list.","example":["architecto"],"items":{"type":"string"}}},"required":["subject","body"]},"metadata":{"type":"object","description":"Echoed verbatim on every webhook payload —\n  the right place to round-trip your shop's order ID.","example":[],"properties":[],"nullable":true}},"required":["client","invoice","send"]}}}}}},"/api/v1/clients":{"get":{"summary":"List clients","operationId":"listClients","description":"Returns the team's clients, cursor-paginated.","parameters":[{"in":"query","name":"limit","description":"Page size (max 100).","example":25,"required":false,"schema":{"type":"integer","description":"Page size (max 100).","example":25}},{"in":"query","name":"cursor","description":"Opaque cursor returned in `meta.next_cursor`. Example:","example":"architecto","required":false,"schema":{"type":"string","description":"Opaque cursor returned in `meta.next_cursor`. Example:","example":"architecto"}}],"responses":{"401":{"description":"","content":{"application/problem+json":{"schema":{"type":"object","example":{"type":"https://accounting.example/problems/unauthenticated","title":"Unauthenticated","status":401,"detail":"Unauthenticated."},"properties":{"type":{"type":"string","example":"https://accounting.example/problems/unauthenticated"},"title":{"type":"string","example":"Unauthenticated"},"status":{"type":"integer","example":401},"detail":{"type":"string","example":"Unauthenticated."}}}}}}},"tags":["Clients"]},"post":{"summary":"Create a client","operationId":"createAClient","description":"","parameters":[{"in":"header","name":"Idempotency-Key","description":"","example":"Required. Unique per logical request; replays return the original response with `Idempotency-Replay: true`.","schema":{"type":"string"}}],"responses":{"201":{"description":"created","content":{"application/json":{"schema":{"type":"object","example":{"data":{"id":"0193f7b0-1b8a-7b7d-9ad0-0c7b5b1d5f3e","name":"Acme GmbH"}},"properties":{"data":{"type":"object","properties":{"id":{"type":"string","example":"0193f7b0-1b8a-7b7d-9ad0-0c7b5b1d5f3e"},"name":{"type":"string","example":"Acme GmbH"}}}}}}}}},"tags":["Clients"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"team_id":{"type":"integer","description":"The <code>id</code> of an existing record in the teams table.","example":16},"name":{"type":"string","description":"Display name of the client.","example":"Acme GmbH"},"name_addition":{"type":"string","description":"Second line of the company name.","example":"Berlin Office","nullable":true},"address":{"type":"string","description":"Street address.","example":"Musterstraße 1"},"address_addition":{"type":"string","description":"Optional address line 2.","example":"architecto","nullable":true},"zip":{"type":"string","description":"Postal code.","example":"10115"},"city":{"type":"string","description":"City.","example":"Berlin"},"country":{"type":"string","description":"ISO 3166-1 alpha-2 country code.","example":"DE"},"email":{"type":"string","description":"Primary billing email.","example":"billing@acme.example","nullable":true},"email_cc":{"type":"array","description":"Optional CC list.","example":["accounting@acme.example"],"items":{"type":"string"}},"email_text":{"type":"string","description":"Default cover text used when emailing invoices to this client.","example":"architecto","nullable":true},"language":{"type":"string","description":"UI / mail language for this client.","example":"de","enum":["de","en"]},"rate":{"type":"integer","description":"Hourly rate in euro cents.","example":12000},"vat":{"type":"number","description":"VAT percentage (0–100).","example":19},"default_position_mode":{"type":"string","description":"Default mode for new invoice rows.","example":"hourly","enum":["hourly","fixed"]},"invoice_note":{"type":"string","description":"Default note appended to invoices for this client.","example":"architecto","nullable":true},"iban":{"type":"string","description":"SEPA IBAN (max 34 chars).","example":"architecto","nullable":true},"bic":{"type":"string","description":"SEPA BIC (max 11 chars).","example":"architecto","nullable":true},"vat_id":{"type":"string","description":"Buyer VAT identification number.","example":"architecto","nullable":true},"buyer_reference":{"type":"string","description":"Required for XRechnung — usually the Leitweg-ID.","example":"04011000-12345-34","nullable":true},"e_invoice_format":{"type":"string","description":"Default e-invoice format.","example":"xrechnung","enum":["none","zugferd","xrechnung"]},"external_id":{"type":"string","description":"value darf nicht länger als 255 Zeichen sein.","example":"j","nullable":true}},"required":["team_id","name","address","zip","city","country","language","rate","vat","default_position_mode"]}}}}}},"/api/v1/clients/{id}":{"get":{"summary":"Get a client","operationId":"getAClient","description":"","parameters":[],"responses":{"401":{"description":"","content":{"application/problem+json":{"schema":{"type":"object","example":{"type":"https://accounting.example/problems/unauthenticated","title":"Unauthenticated","status":401,"detail":"Unauthenticated."},"properties":{"type":{"type":"string","example":"https://accounting.example/problems/unauthenticated"},"title":{"type":"string","example":"Unauthenticated"},"status":{"type":"integer","example":401},"detail":{"type":"string","example":"Unauthenticated."}}}}}}},"tags":["Clients"]},"patch":{"summary":"Update a client","operationId":"updateAClient","description":"Same body shape as create. Pass only the fields you want to change.","parameters":[],"responses":[],"tags":["Clients"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"team_id":{"type":"integer","description":"The <code>id</code> of an existing record in the teams table.","example":16},"name":{"type":"string","description":"value darf nicht länger als 255 Zeichen sein.","example":"n"},"name_addition":{"type":"string","description":"value darf nicht länger als 255 Zeichen sein.","example":"g","nullable":true},"address":{"type":"string","description":"value darf nicht länger als 255 Zeichen sein.","example":"z"},"address_addition":{"type":"string","description":"value darf nicht länger als 255 Zeichen sein.","example":"m","nullable":true},"zip":{"type":"string","description":"value darf nicht länger als 20 Zeichen sein.","example":"iyvdljnikhwaykcm"},"city":{"type":"string","description":"value darf nicht länger als 255 Zeichen sein.","example":"y"},"country":{"type":"string","description":"value muss 2 Zeichen lang sein.","example":"uw"},"email":{"type":"string","description":"value muss eine gültige E-Mail-Adresse sein. value darf nicht länger als 255 Zeichen sein.","example":"theo.hauck@example.com","nullable":true},"email_cc":{"type":"array","description":"value muss eine gültige E-Mail-Adresse sein. value darf nicht länger als 255 Zeichen sein.","example":["w"],"items":{"type":"string"}},"email_text":{"type":"string","description":"","example":"architecto","nullable":true},"language":{"type":"string","description":"","example":"en","enum":["de","en"]},"rate":{"type":"integer","description":"value muss mindestens 0 sein.","example":39},"vat":{"type":"number","description":"value muss mindestens 0 sein. value darf nicht größer als 100 sein.","example":7},"default_position_mode":{"type":"string","description":"","example":"fixed","enum":["hourly","fixed"]},"invoice_note":{"type":"string","description":"","example":"architecto","nullable":true},"iban":{"type":"string","description":"value darf nicht länger als 34 Zeichen sein.","example":"n","nullable":true},"bic":{"type":"string","description":"value darf nicht länger als 11 Zeichen sein.","example":"gzmiyv","nullable":true},"vat_id":{"type":"string","description":"value darf nicht länger als 64 Zeichen sein.","example":"d","nullable":true},"buyer_reference":{"type":"string","description":"value darf nicht länger als 255 Zeichen sein.","example":"l","nullable":true},"e_invoice_format":{"type":"string","description":"","example":"xrechnung","enum":["none","zugferd","xrechnung"]},"external_id":{"type":"string","description":"value darf nicht länger als 255 Zeichen sein.","example":"j","nullable":true}},"required":["team_id","name","address","zip","city","country","language","rate","vat","default_position_mode"]}}}}},"delete":{"summary":"Delete a client","operationId":"deleteAClient","description":"Soft-delete. Refused with 409 if the client has any invoice still inside its 10-year legal-retention window.","parameters":[],"responses":{"204":{"description":"deleted","content":{"text/plain":{"schema":{"type":"string","example":""}}}},"409":{"description":"retained invoices exist","content":{"application/json":{"schema":{"type":"object","example":{"type":"https://accounting.example/problems/client-has-retained-invoices","title":"Client has retained invoices","status":409},"properties":{"type":{"type":"string","example":"https://accounting.example/problems/client-has-retained-invoices"},"title":{"type":"string","example":"Client has retained invoices"},"status":{"type":"integer","example":409}}}}}}},"tags":["Clients"]},"parameters":[{"in":"path","name":"id","description":"The ID of the client.","example":"014b17a6-c8e3-45c2-a2ff-78b1449daa26","required":true,"schema":{"type":"string"}},{"in":"path","name":"client","description":"Client UUID.","example":"architecto","required":true,"schema":{"type":"string"}}]},"/api/v1/invoices":{"get":{"summary":"List invoices","operationId":"listInvoices","description":"Returns the team's invoices, cursor-paginated. Drafts are hidden by default — pass `?include=drafts` (or `?include_drafts=1`) to include them.","parameters":[{"in":"query","name":"limit","description":"Page size (max 100).","example":25,"required":false,"schema":{"type":"integer","description":"Page size (max 100).","example":25}},{"in":"query","name":"cursor","description":"Opaque cursor returned in `meta.next_cursor`. Example:","example":"architecto","required":false,"schema":{"type":"string","description":"Opaque cursor returned in `meta.next_cursor`. Example:","example":"architecto"}},{"in":"query","name":"include","description":"Use `drafts` to include draft invoices.","example":"drafts","required":false,"schema":{"type":"string","description":"Use `drafts` to include draft invoices.","example":"drafts"}}],"responses":{"401":{"description":"","content":{"application/problem+json":{"schema":{"type":"object","example":{"type":"https://accounting.example/problems/unauthenticated","title":"Unauthenticated","status":401,"detail":"Unauthenticated."},"properties":{"type":{"type":"string","example":"https://accounting.example/problems/unauthenticated"},"title":{"type":"string","example":"Unauthenticated"},"status":{"type":"integer","example":401},"detail":{"type":"string","example":"Unauthenticated."}}}}}}},"tags":["Invoices"]},"post":{"summary":"Create an invoice","operationId":"createAnInvoice","description":"","parameters":[{"in":"header","name":"Idempotency-Key","description":"","example":"Required.","schema":{"type":"string"}}],"responses":[],"tags":["Invoices"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"team_id":{"type":"integer","description":"The <code>id</code> of an existing record in the teams table.","example":16},"client_id":{"type":"string","description":"UUID of the client to bill.","example":"architecto"},"type":{"type":"string","description":"Document type.","example":"invoice","enum":["invoice","offer"]},"tax_rate":{"type":"number","description":"VAT percentage (0–100).","example":19},"billing_date":{"type":"date","description":"Billing date (`YYYY-MM-DD`). Defaults to today server-side.","example":"2026-05-07","nullable":true},"billing_period":{"type":"string","description":"value muss 7 Zeichen lang sein.","example":"miyvdlj","nullable":true},"due_offset_days":{"type":"integer","description":"Days from `billing_date` to `due_at`.","example":14},"due_at":{"type":"string","description":"value ist kein gültiges Datum.","example":"2026-05-18T10:08:43","nullable":true},"paid_at":{"type":"string","description":"value ist kein gültiges Datum.","example":"2026-05-18T10:08:43","nullable":true},"every":{"type":"string","description":"Recurrence interval. Forces `type=invoice`.","example":"daily","enum":["daily","weekly","biweekly","monthly","bimonthly","quarterly","semiannual","yearly. Example:"],"nullable":true},"next_invoice_at":{"type":"date","description":"Required when `every` is set. Example:","example":"architecto","nullable":true},"is_draft":{"type":"boolean","description":"Save as draft (no number issued yet). Cannot be combined with `every`.","example":false},"subject":{"type":"string","description":"Optional subject line.","example":"architecto","nullable":true},"salutation":{"type":"string","description":"Optional salutation.","example":"architecto","nullable":true},"description":{"type":"string","description":"Long-form description rendered above the position table.","example":"Eius et animi quos velit et.","nullable":true},"note":{"type":"string","description":"Footer note rendered below the totals.","example":"architecto","nullable":true},"metadata":{"type":"object","description":"","example":null,"properties":[],"nullable":true},"positions":{"type":"array","description":"At least one row.","example":[[]],"items":{"type":"object","properties":{"id":{"type":"string","description":"UUID identifying the row.","example":"architecto"},"date":{"type":"date","description":"Position date.","example":"architecto"},"mode":{"type":"string","description":"Pricing mode.","example":"hourly","enum":["hourly","fixed"]},"description":{"type":"string","description":"Description rendered on the PDF.","example":"Eius et animi quos velit et."},"hours":{"type":"number","description":"Required when `mode=hourly`; prohibited when `mode=fixed`.","example":4326.41688,"nullable":true},"amount_cents":{"type":"integer","description":"Required when `mode=fixed`; prohibited when `mode=hourly`.","example":16,"nullable":true}},"required":["id","date","mode","description"]}}},"required":["team_id","client_id","type","tax_rate","positions"]}}}}}},"/api/v1/invoices/{id}":{"get":{"summary":"Get an invoice","operationId":"getAnInvoice","description":"","parameters":[],"responses":{"401":{"description":"","content":{"application/problem+json":{"schema":{"type":"object","example":{"type":"https://accounting.example/problems/unauthenticated","title":"Unauthenticated","status":401,"detail":"Unauthenticated."},"properties":{"type":{"type":"string","example":"https://accounting.example/problems/unauthenticated"},"title":{"type":"string","example":"Unauthenticated"},"status":{"type":"integer","example":401},"detail":{"type":"string","example":"Unauthenticated."}}}}}}},"tags":["Invoices"]},"patch":{"summary":"Update an invoice","operationId":"updateAnInvoice","description":"Same body shape as create. A draft invoice gets its gapless number assigned the moment `is_draft` flips to `false`.","parameters":[],"responses":[],"tags":["Invoices"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"team_id":{"type":"integer","description":"The <code>id</code> of an existing record in the teams table.","example":16},"client_id":{"type":"string","description":"value muss eine gültige UUID sein. The <code>id</code> of an existing record in the clients table.","example":"a4855dc5-0acb-33c3-b921-f4291f719ca0"},"type":{"type":"string","description":"","example":"invoice","enum":["invoice","offer"]},"tax_rate":{"type":"number","description":"value muss mindestens 0 sein. value darf nicht größer als 100 sein.","example":16},"billing_date":{"type":"string","description":"value ist kein gültiges Datum.","example":"2026-05-18T10:08:43","nullable":true},"billing_period":{"type":"string","description":"value muss 7 Zeichen lang sein.","example":"miyvdlj","nullable":true},"due_offset_days":{"type":"integer","description":"value muss mindestens 0 sein. value darf nicht größer als 255 sein.","example":17},"due_at":{"type":"string","description":"value ist kein gültiges Datum.","example":"2026-05-18T10:08:43","nullable":true},"paid_at":{"type":"string","description":"value ist kein gültiges Datum.","example":"2026-05-18T10:08:43","nullable":true},"every":{"type":"string","description":"","example":"monthly","enum":["daily","weekly","biweekly","monthly","bimonthly","quarterly","semiannual","yearly"],"nullable":true},"next_invoice_at":{"type":"string","description":"This field is required when <code>every</code> is present. value ist kein gültiges Datum. value muss ein Datum nach oder gleich dem <code>billing_date</code> sein.","example":"2052-06-10","nullable":true},"is_draft":{"type":"boolean","description":"","example":true},"subject":{"type":"string","description":"value darf nicht länger als 255 Zeichen sein.","example":"n","nullable":true},"salutation":{"type":"string","description":"value darf nicht länger als 255 Zeichen sein.","example":"g","nullable":true},"description":{"type":"string","description":"","example":"Eius et animi quos velit et.","nullable":true},"note":{"type":"string","description":"","example":"architecto","nullable":true},"metadata":{"type":"object","description":"","example":null,"properties":[],"nullable":true},"positions":{"type":"array","description":"value muss mindestens 1 Elemente enthalten.","example":[[]],"items":{"type":"object","properties":{"id":{"type":"string","description":"value muss eine gültige UUID sein.","example":"6ff8f7f6-1eb3-3525-be4a-3932c805afed"},"date":{"type":"string","description":"value ist kein gültiges Datum.","example":"2026-05-18T10:08:43"},"mode":{"type":"string","description":"","example":"hourly","enum":["hourly","fixed"]},"description":{"type":"string","description":"value darf nicht länger als 1000 Zeichen sein.","example":"Animi quos velit et fugiat."},"hours":{"type":"number","description":"","example":4326.41688,"nullable":true},"amount_cents":{"type":"integer","description":"","example":16,"nullable":true}},"required":["id","date","mode","description"]}}},"required":["team_id","client_id","type","tax_rate","due_offset_days","positions"]}}}}},"delete":{"summary":"Delete an invoice","operationId":"deleteAnInvoice","description":"Soft-delete, restricted to true drafts. Finalised / sent invoices must\nbe reversed via Stornorechnung — `POST /v1/invoices/{id}/cancel`.\nRecurring templates must be stopped via\n`POST /v1/invoices/{id}/stop-recurring`.","parameters":[],"responses":{"204":{"description":"deleted","content":{"text/plain":{"schema":{"type":"string","example":""}}}},"422":{"description":"","content":{"text/plain":{"schema":{"oneOf":[{"description":"finalised invoice — use /cancel","type":"string","example":""},{"description":"recurring template — use /stop-recurring","type":"string","example":""}]}}}}},"tags":["Invoices"]},"parameters":[{"in":"path","name":"id","description":"The ID of the invoice.","example":"019e167c-cbf7-73fe-94e0-c99b6907e435","required":true,"schema":{"type":"string"}},{"in":"path","name":"invoice","description":"Invoice UUID.","example":"architecto","required":true,"schema":{"type":"string"}}]},"/api/v1/invoices/{invoice_id}/pdf":{"get":{"summary":"Download the invoice PDF","operationId":"downloadTheInvoicePDF","description":"Returns the rendered PDF as `application/pdf`. The response carries an `ETag` keyed on `(invoice.id, invoice.updated_at)`. Send `If-None-Match: <etag>` to get a 304 when the PDF hasn't changed. The PDF is regenerated on cache miss and stored on the `pdf-cache` disk for 7 days.","parameters":[],"responses":{"200":{"description":"pdf bytes","content":{"text/plain":{"schema":{"type":"string","example":""}}}},"304":{"description":"not modified","content":{"text/plain":{"schema":{"type":"string","example":""}}}}},"tags":["Invoices"]},"parameters":[{"in":"path","name":"invoice_id","description":"The ID of the invoice.","example":"019e167c-cbf7-73fe-94e0-c99b6907e435","required":true,"schema":{"type":"string"}},{"in":"path","name":"invoice","description":"Invoice UUID.","example":"architecto","required":true,"schema":{"type":"string"}}]},"/api/v1/invoices/{invoice_id}/send":{"post":{"summary":"Send an invoice by email","operationId":"sendAnInvoiceByEmail","description":"Queues a `SendInvoiceMail` job. The mail is rendered in the **client's** language (`clients.language`), not the caller's. Returns `202 Accepted` immediately with a `mail_event_id` that can be correlated with the `mail_events` table once the worker has run.","parameters":[{"in":"header","name":"Idempotency-Key","description":"","example":"Required.","schema":{"type":"string"}}],"responses":{"202":{"description":"queued","content":{"application/json":{"schema":{"type":"object","example":{"mail_event_id":"0193f7b0-1b8a-7b7d-9ad0-0c7b5b1d5f3e","status":"queued"},"properties":{"mail_event_id":{"type":"string","example":"0193f7b0-1b8a-7b7d-9ad0-0c7b5b1d5f3e"},"status":{"type":"string","example":"queued"}}}}}}},"tags":["Invoices"],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"cc":{"type":"array","description":"Optional CC recipients.","example":["architecto"],"items":{"type":"string"}},"subject":{"type":"string","description":"Mail subject.","example":"Your invoice INV-2026-0042"},"body":{"type":"string","description":"Mail body (plain text).","example":"Please find attached…"},"attachments":{"type":"array","description":"Optional extra attachments. Max 25 MB each. Allowed types: pdf, png, jpg, jpeg, doc, docx, xlsx.","items":{"type":"string","format":"binary"}}},"required":["subject","body"]}}}}},"parameters":[{"in":"path","name":"invoice_id","description":"The ID of the invoice.","example":"019e167c-cbf7-73fe-94e0-c99b6907e435","required":true,"schema":{"type":"string"}},{"in":"path","name":"invoice","description":"Invoice UUID.","example":"architecto","required":true,"schema":{"type":"string"}}]},"/api/v1/invoices/{invoice_id}/mark-paid":{"post":{"summary":"Mark an invoice as paid","operationId":"markAnInvoiceAsPaid","description":"","parameters":[{"in":"header","name":"Idempotency-Key","description":"","example":"Required.","schema":{"type":"string"}}],"responses":[],"tags":["Invoices"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"paid_at":{"type":"date","description":"Optional payment date. Defaults to today.","example":"2026-05-07","nullable":true}}}}}}},"parameters":[{"in":"path","name":"invoice_id","description":"The ID of the invoice.","example":"019e167c-cbf7-73fe-94e0-c99b6907e435","required":true,"schema":{"type":"string"}},{"in":"path","name":"invoice","description":"Invoice UUID.","example":"architecto","required":true,"schema":{"type":"string"}}]},"/api/v1/invoices/{invoice_id}/stop-recurring":{"post":{"summary":"Stop a recurring invoice template","operationId":"stopARecurringInvoiceTemplate","description":"Sets `every` to `null` so the scheduler stops generating further\nchildren, and stamps `recurring_stopped_at`. The template stays visible\nwith its full history. Idempotent: a second call returns the same\nresource.","parameters":[{"in":"header","name":"Idempotency-Key","description":"","example":"Required.","schema":{"type":"string"}}],"responses":[],"tags":["Invoices"]},"parameters":[{"in":"path","name":"invoice_id","description":"The ID of the invoice.","example":"019e167c-cbf7-73fe-94e0-c99b6907e435","required":true,"schema":{"type":"string"}},{"in":"path","name":"invoice","description":"Invoice UUID.","example":"architecto","required":true,"schema":{"type":"string"}}]},"/api/v1/invoices/{invoice_id}/change-cycle":{"post":{"summary":"Change the cadence of a recurring invoice template","operationId":"changeTheCadenceOfARecurringInvoiceTemplate","description":"Mutates `every` (required) and `next_invoice_at` (optional — derived\nfrom current `next_invoice_at` + new cadence when omitted). Rejected\nwith 422 if `every` is currently `null` (the template was stopped —\nstart a new one instead). Idempotent: a second call with the same\npayload returns the same resource.","parameters":[{"in":"header","name":"Idempotency-Key","description":"","example":"Required.","schema":{"type":"string"}}],"responses":[],"tags":["Invoices"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"every":{"type":"string","description":"New recurrence interval.","example":"yearly","enum":["daily","weekly","biweekly","monthly","bimonthly","quarterly","semiannual","yearly"]},"next_invoice_at":{"type":"date","description":"Optional override for the next run. Defaults to `next_invoice_at` advanced by the new cadence.","example":"2026-06-15","nullable":true}},"required":["every"]}}}}},"parameters":[{"in":"path","name":"invoice_id","description":"The ID of the invoice.","example":"019e167c-cbf7-73fe-94e0-c99b6907e435","required":true,"schema":{"type":"string"}},{"in":"path","name":"invoice","description":"Invoice UUID.","example":"architecto","required":true,"schema":{"type":"string"}}]},"/api/v1/invoices/{invoice_id}/cancel":{"post":{"summary":"Cancel an invoice via Stornorechnung","operationId":"cancelAnInvoiceViaStornorechnung","description":"Issues a Stornorechnung (negative-amount reversing invoice) that\nlegally cancels the original under UStG §14c / GoBD. Returns the\nnew Stornorechnung resource (not the original). Idempotent via the\n`Idempotency-Key` header: a second call with the same key replays\nthe same response. Rejected with 422 if the invoice is not in a\ncancellable state (draft, offer, already a Storno, already cancelled).","parameters":[{"in":"header","name":"Idempotency-Key","description":"","example":"Required.","schema":{"type":"string"}}],"responses":[],"tags":["Invoices"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":"string","description":"Optional free-text cancellation reason, stored on the Stornorechnung's metadata.","example":"customer downgraded"}}}}}}},"parameters":[{"in":"path","name":"invoice_id","description":"The ID of the invoice.","example":"019e167c-cbf7-73fe-94e0-c99b6907e435","required":true,"schema":{"type":"string"}},{"in":"path","name":"invoice","description":"Invoice UUID.","example":"architecto","required":true,"schema":{"type":"string"}}]},"/api/v1/recurring":{"get":{"summary":"30-day recurring forecast","operationId":"30DayRecurringForecast","description":"Returns recurring invoices whose `next_invoice_at` falls within the next 30 days, ordered by date.","parameters":[],"responses":{"401":{"description":"","content":{"application/problem+json":{"schema":{"type":"object","example":{"type":"https://accounting.example/problems/unauthenticated","title":"Unauthenticated","status":401,"detail":"Unauthenticated."},"properties":{"type":{"type":"string","example":"https://accounting.example/problems/unauthenticated"},"title":{"type":"string","example":"Unauthenticated"},"status":{"type":"integer","example":401},"detail":{"type":"string","example":"Unauthenticated."}}}}}}},"tags":["Recurring"]}}}}