{"openapi":"3.0.3","info":{"title":"Claribill API v1","version":"1.0.0","description":"Claribill's public REST API for integrations with accounting software, shops, CRMs and custom applications.\n\nInteractive reference: [API explorer](https://www.claribill.com/en/api-reference) · User documentation: [Documentation](https://www.claribill.com/en/docs)\n\n## Authentication\n\nSend an API key in the `Authorization: Bearer sk_live_...` header with every request. Create keys: Settings → API keys (/dashboard/settings/api-keys), owners and admins only. Each key belongs to exactly one company and has scopes; all data is limited to that company.\n\n## Plans and quotas\n\nAPI access is included in Professional (50,000 calls/month) and Enterprise (250,000 calls/month). Free and Starter do not include API access.\n\n## Rate limit\n\n100 requests per minute per API key.\n\n## Response format and pagination\n\nAll v1 endpoints that return data wrap the payload in { data: … }. List endpoints also return { data: [...], pagination: { total, limit, offset, hasMore } } (or meta for some resources). Successful actions without a return object respond with { success: true }. Blog routes under /api/v1/blog/** (platform admins only) are an exception: lists use their own key ({ posts }, { categories }, { tags }, { media }), individual objects have no envelope, AI jobs use { job } and deletions return { ok: true }.\n\nList endpoints accept `limit` (default 50, maximum 100) and `offset`.\n\n## Errors\n\nErrors use the format `{ error: string, code?: string, allowedValues?: string[] }`.\n\n- **400** – Invalid input: required field, enum value, number/date format or business rule.\n\n- **401** – Authorization header missing, not a Bearer header, or the key is invalid/inactive.\n\n- **403** – Missing scope (requiredScopes in the response), plan without API access (code: \"plan_no_access\"), or a plan limit is exhausted.\n\n- **404** – The record does not exist or belongs to another company. Other companies' data is never returned.\n\n- **409** – State conflict: incorrect status for the action, concurrent change, or a number/reference already taken.\n\n- **429** – Monthly quota exhausted (code: \"quota_exceeded\") or rate limit reached.\n\n- **500** – Unexpected internal error.\n\n- **502** – A dependent external service failed, such as email delivery or S3.\n\n## Field names\n\nRequest bodies generally accept both camelCase and snake_case for the same field, for example customerNumber = customer_number. Newer resources use camelCase throughout. For compatibility, the four legacy resources above retain their original snake_case response shape, with camelCase aliases for new fields.","termsOfService":"https://www.claribill.com/agb","contact":{"name":"Claribill Support","url":"https://www.claribill.com/en/support"}},"externalDocs":{"description":"Claribill documentation","url":"https://www.claribill.com/en/docs"},"servers":[{"url":"https://www.claribill.com","description":"Production"}],"tags":[{"name":"invoices","description":"Invoices – Invoices and proformas, including payments, reminders, corrections, versions and Factur-X/ZUGFeRD export."},{"name":"credit-notes","description":"Credit notes – Standalone credit notes, credit notes linked to an invoice, and their payouts. Credit notes are Invoice rows and share invoices:read/invoices:write scopes."},{"name":"customers","description":"Customers – Customer details including addresses, bank details, SEPA mandate, VAT validation through VIES and duplicate search."},{"name":"products","description":"Products – Product and service catalogue for pre-filling line items."},{"name":"quotes","description":"Quotes – Quotes, including delivery, status changes and conversion to an invoice or order confirmation."},{"name":"order-confirmations","description":"Order confirmations – Order confirmations, including delivery, status changes and conversion to an invoice or order."},{"name":"delivery-notes","description":"Delivery notes – Delivery notes without prices, including delivery and conversion to an invoice with prices added later."},{"name":"orders","description":"Orders – Long-running orders with a budget, partial deliveries and partial invoices."},{"name":"recurring-schedules","description":"Recurring invoices – Schedules for automatically generated invoices: subscriptions, maintenance contracts and rent."},{"name":"documents","description":"Documents (file storage) – Central file storage for supporting documents, including direct S3 uploads."},{"name":"exports","description":"Exports – Accounting exports (CSV, DATEV, BMD, UStVA) and SEPA direct debit files."},{"name":"company","description":"Company details – Company details, number ranges, plan features and usage; read-only."},{"name":"webhooks","description":"Webhooks – Webhook endpoints for event notifications, Enterprise plan only."},{"name":"blog","description":"Blog (platform admins only) – Internal CMS for the Claribill blog. Uses lib/blog/api-auth.ts; the API key must also belong to a platform admin. Not a customer feature; only methods, paths and scopes are listed here."},{"name":"meta","description":"Metadata and discovery – Machine-readable description of the API itself."}],"paths":{"/api/v1/invoices":{"get":{"operationId":"get_invoices","tags":["invoices"],"summary":"List invoices, including credit notes and proformas","description":"**Scope:** `invoices:read`\n\n**Response:** { data: Invoice[], pagination }. Includes credit notes and proformas when invoice_type is omitted. Sorted by issueDate descending.\n\n- All queries are scoped to the API key's company.\n- Invalid status/invoice_type → 400 with allowedValues.","parameters":[{"name":"status","in":"query","required":false,"description":"Filter by status, case-insensitive.","schema":{"type":"string","enum":["DRAFT","FINAL","SENT","PAID","OVERDUE","CANCELLED"]}},{"name":"invoice_type","in":"query","required":false,"description":"Filter by document type. Alias: `invoiceType`.","schema":{"type":"string","enum":["INVOICE","CREDIT_NOTE","PROFORMA"]}},{"name":"customer_id","in":"query","required":false,"description":"Filter by customer. Alias: `customerId`.","schema":{"type":"string"}},{"name":"search","in":"query","required":false,"description":"Case-insensitive partial match on document number or customer name.","schema":{"type":"string"}},{"name":"from","in":"query","required":false,"description":"issueDate ≥ from.","schema":{"type":"string","format":"date"}},{"name":"to","in":"query","required":false,"description":"issueDate ≤ to.","schema":{"type":"string","format":"date"}},{"name":"category_id","in":"query","required":false,"description":"Filter by document category.","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Page size.","schema":{"type":"number","default":50}},{"name":"offset","in":"query","required":false,"description":"Offset.","schema":{"type":"number","default":0}}],"responses":{"200":{"description":"{ data: Invoice[], pagination }. Includes credit notes and proformas when invoice_type is omitted. Sorted by issueDate descending.","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Invoice"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["invoices:read"]},"post":{"operationId":"post_invoices","tags":["invoices"],"summary":"Create an invoice or proforma as a draft","description":"**Scope:** `invoices:write`\n\n**Response:** 201 { data: Invoice }, as in GET detail, without payments.\n\n- Creates a draft with an ENTWURF-… placeholder number. The actual number is assigned on transition to FINAL/SENT or POST …/send.\n- Use Idempotency-Key to retry safely after a timeout or connection loss. An invalid format returns 422 invalid_idempotency_key; if the invoice associated with the key has been deleted, the response is 409 idempotency_key_invoice_deleted. A replay does not check the document quota or emit a webhook. If the first request stops after creating the invoice, its invoice.created webhook may be missing. An invalid Idempotency-Key is rejected rather than ignored.\n- Totals use prepareInvoiceTaxPayload, as in the dashboard: absolute line discounts, tax rates normalised to the tax country, and automatic OSS resolution if no flag is supplied.\n- 400 for missing required fields, invalid enums or a discount exceeding the line amount (lineIndex in the response); 404 Customer/Company/Document category not found.\n- Audit INVOICE_CREATED_VIA_API. Webhook invoice.created.","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, 8–200 characters from A–Z, a–z, 0–9, . _ : -. Reusing the same key and body returns the same invoice in its current state (201, Idempotent-Replayed: true header), instead of creating another. The same key with a different body returns 422 idempotency_key_reused. The entire body is compared, including fields the server does not use. Keys are scoped to the company; concurrent calls with the same key create exactly one invoice. A key remains effective for at least 30 days. After deletion, reusing it creates a new invoice.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"documentLocale":{"type":"string","enum":["de","en"],"description":"On creation only: explicit document language takes precedence over customer/company defaults, then German. Derived documents retain the source language. The response contains documentLocale and documentTextVersion; editing does not change the stored profile. Alias: `document_locale`."},"customerId":{"type":"string","description":"Must belong to the company."},"lines":{"type":"array","items":{"type":"object","properties":{"description":{"type":"string","description":"Line item description."},"quantity":{"type":"number","default":1,"description":"Quantity."},"unitPrice":{"type":"number","default":0,"description":"Net unit price. Alias: `unit_price`."},"taxRate":{"type":"number","description":"Tax rate in %. Alias: `tax_rate`. Default: Standard rate of the tax country."},"discount":{"type":"number","default":0,"description":"Absolute discount amount, not a percentage."},"lineType":{"type":"string","enum":["PRODUCT","SERVICE","DISCOUNT_PERCENT","DISCOUNT_ABSOLUTE","SHIPPING","ADVANCE_PAYMENT","CREDIT","TEXT","SUBTOTAL","PAGEBREAK"],"default":"PRODUCT","description":"Line item type. Alias: `line_type`."}}},"description":"At least one line item."},"invoiceType":{"type":"string","enum":["INVOICE","PROFORMA"],"default":"INVOICE","description":"CREDIT_NOTE → 400, with a reference to POST /api/v1/credit-notes. Alias: `invoice_type`."},"issueDate":{"type":"string","format":"date","description":"Invoice date. Default: today."},"dueDate":{"type":"string","format":"date","description":"Due date. Default: customer.paymentTermDays ?? company.paymentTermDays ?? 14 days."},"servicePeriodStart":{"type":"string","format":"date","nullable":true,"description":"Service period."},"servicePeriodEnd":{"type":"string","format":"date","nullable":true,"description":"Service period."},"currency":{"type":"string","default":"EUR","description":"Currency code."},"reverseCharge":{"type":"boolean","default":false,"description":"VAT liability of the recipient."},"taxExemptionReason":{"type":"string","enum":["NONE","SMALL_BUSINESS","REVERSE_CHARGE_EU","REVERSE_CHARGE_EU_NO_VAT","THIRD_COUNTRY","INTRA_COMMUNITY","EXPORT_DELIVERY","REVERSE_CHARGE_13B","INTERNAL_SUPPLY","OTHER_EXEMPTION"],"description":"Invalid → 400."},"taxExemptionNote":{"type":"string","description":"Free text about the exemption."},"taxCountry":{"type":"string","description":"Tax or delivery country."},"deliveryCountry":{"type":"string","description":"Tax or delivery country."},"ossApplicable":{"type":"boolean","description":"If omitted, OSS is derived from the tax context. Alias: `oss_applicable`."},"ossCountry":{"type":"string","description":"OSS destination country. Alias: `oss_country`."},"notes":{"type":"string","description":"Internal note, not printed."},"footer":{"type":"string","description":"Closing text (printed)"},"headerText":{"type":"string","description":"Opening text (printed) Alias: `header_text`."},"customTitle":{"type":"string","description":"Custom document title instead of Invoice. Alias: `custom_title`."},"pdfWithoutLetterhead":{"type":"boolean","default":false,"description":"PDF without letterhead. Alias: `pdf_without_letterhead`."},"invoiceDateIsServiceDate":{"type":"boolean","default":false,"description":"Invoice date equals service date. Alias: `invoice_date_is_service_date`."},"useShippingAddress":{"type":"boolean","default":false,"description":"Enables shippingName/-Street/-ZipCode/-City/-Country. Alias: `use_shipping_address`."},"documentCategoryId":{"type":"string","nullable":true,"description":"Document category. Alias: `document_category_id`."},"orderNumber":{"type":"string","description":"Purchase order number/order reference. Alias: `order_number`."},"buyerReference":{"type":"string","description":"Buyer reference or Leitweg-ID. Alias: `buyer_reference`."},"eInvoiceAudit":{"type":"object","description":"Audit data for format, submission channel, portal URL and status."},"e_invoice":{"type":"object","description":"Audit data for format, submission channel, portal URL and status."},"metadata":{"type":"object","description":"Custom JSON object."}},"required":["customerId","lines"]}}}},"responses":{"201":{"description":"201 { data: Invoice }, as in GET detail, without payments.","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Invoice"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["invoices:write"]}},"/api/v1/invoices/{id}":{"get":{"operationId":"get_invoices_id","tags":["invoices"],"summary":"Read invoice details","description":"**Scope:** `invoices:read`\n\n**Response:** { data: Invoice } including footer, sentAt, paidAt, lockedAt, customer (with address/VAT ID), lines[], payments[] and paidAmount.\n\n- 404 Invoice not found, including for other companies.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"{ data: Invoice } including footer, sentAt, paidAt, lockedAt, customer (with address/VAT ID), lines[], payments[] and paidAmount.","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Invoice"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["invoices:read"]},"put":{"operationId":"put_invoices_id","tags":["invoices"],"summary":"Update an invoice and/or change its status","description":"**Scope:** `invoices:write`\n\n**Response:** 200 { data: { id, invoiceNumber, status, netTotal, taxTotal, grossTotal, orderNumber, buyerReference, e_invoice, updatedAt, [stornoInvoiceId, stornoInvoiceNumber] } }\n\n- When the document leaves draft status, it counts towards the plan document quota: an exhausted quota returns 403 plan_limit_reached (cancelling a draft remains possible).\n- Status transitions use lib/invoice-status-transitions.ts: DRAFT→FINAL|SENT|CANCELLED, FINAL→SENT|DRAFT|CANCELLED, SENT→PAID|OVERDUE|CANCELLED, OVERDUE→PAID|CANCELLED; PAID/CANCELLED are final states. Invalid → 400 with allowedTransitions.\n- DRAFT→FINAL/SENT assigns the actual number atomically from the document type's number range without gaps; →SENT sets sentAt and locks the document (lockedAt, v1 snapshot); FINAL deliberately remains unlocked; →PAID sets paidAt.\n- →CANCELLED automatically creates a cancellation credit note for issued invoices, as with POST …/cancel and createStorno: true, without an email.\n- Content can only change on editable invoices (DRAFT or FINAL and not locked, lib/invoice-lock.ts); otherwise 400 with lockedFields and a reference to the correction endpoint. An unchanged read-modify-write body does not prevent a status transition.\n- Webhooks invoice.sent/invoice.paid/invoice.cancelled on status changes, otherwise invoice.updated; also credit_note.created for automatic cancellation credit notes. Audit INVOICE_UPDATED_VIA_API. Conflicts → 409.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["DRAFT","FINAL","SENT","PAID","OVERDUE","CANCELLED"],"description":"Status change; invalid → 400 with allowedValues."},"documentCategoryId":{"description":"All optional, also accepted as snake_case; content fields only. See locking notes."},"dueDate":{"description":"All optional, also accepted as snake_case; content fields only. See locking notes."},"servicePeriodStart":{"description":"All optional, also accepted as snake_case; content fields only. See locking notes."},"servicePeriodEnd":{"description":"All optional, also accepted as snake_case; content fields only. See locking notes."},"reverseCharge":{"description":"All optional, also accepted as snake_case; content fields only. See locking notes."},"taxExemptionReason":{"description":"All optional, also accepted as snake_case; content fields only. See locking notes."},"taxExemptionNote":{"description":"All optional, also accepted as snake_case; content fields only. See locking notes."},"taxCountry":{"description":"All optional, also accepted as snake_case; content fields only. See locking notes."},"deliveryCountry":{"description":"All optional, also accepted as snake_case; content fields only. See locking notes."},"ossApplicable":{"description":"All optional, also accepted as snake_case; content fields only. See locking notes."},"ossCountry":{"description":"All optional, also accepted as snake_case; content fields only. See locking notes."},"headerText":{"description":"All optional, also accepted as snake_case; content fields only. See locking notes."},"notes":{"description":"All optional, also accepted as snake_case; content fields only. See locking notes."},"footer":{"description":"All optional, also accepted as snake_case; content fields only. See locking notes."},"orderNumber":{"description":"All optional, also accepted as snake_case; content fields only. See locking notes."},"buyerReference":{"description":"All optional, also accepted as snake_case; content fields only. See locking notes."},"customTitle":{"description":"All optional, also accepted as snake_case; content fields only. See locking notes."},"pdfWithoutLetterhead":{"description":"All optional, also accepted as snake_case; content fields only. See locking notes."},"invoiceDateIsServiceDate":{"description":"All optional, also accepted as snake_case; content fields only. See locking notes."},"useShippingAddress":{"description":"All optional, also accepted as snake_case; content fields only. See locking notes."},"eInvoiceAudit":{"description":"All optional, also accepted as snake_case; content fields only. See locking notes."},"e_invoice":{"description":"All optional, also accepted as snake_case; content fields only. See locking notes."},"lines":{"type":"array","items":{"type":"object"},"description":"Replaces all line items (at least one)"}}}}}},"responses":{"200":{"description":"200 { data: { id, invoiceNumber, status, netTotal, taxTotal, grossTotal, orderNumber, buyerReference, e_invoice, updatedAt, [stornoInvoiceId, stornoInvoiceNumber] } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["invoices:write"]},"delete":{"operationId":"delete_invoices_id","tags":["invoices"],"summary":"Delete an invoice draft","description":"**Scope:** `invoices:write`\n\n**Response:** 200 { success: true }\n\n- Drafts only (DRAFT), otherwise 400. Deletes the line items and document in one transaction and releases linked source documents: quote, order confirmation or delivery note.\n- A draft that already has an issued number (for example after a failed email) returns 409 { code: \"invoice_number_issued\", invoiceNumber }. To preserve the numbering sequence, resend it or cancel it by setting status CANCELLED (without a cancellation credit note).\n- Audit INVOICE_DELETED_VIA_API.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"200 { success: true }","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["invoices:write"]}},"/api/v1/invoices/{id}/pdf":{"get":{"operationId":"get_invoices_id_pdf","tags":["invoices"],"summary":"Download an invoice PDF","description":"**Scope:** `invoices:read`\n\n**Response:** 200 PDF binary, Content-Type application/pdf, Content-Disposition inline, header X-PDF-Source: generated|s3-cache.\n\n- Renders through lib/invoice-pdf-bundle.ts using the dashboard layout, deliberately without the S3 cache.\n- 404 Invoice not found. Audit PDF_DOWNLOADED_VIA_API only when found.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"200 PDF binary, Content-Type application/pdf, Content-Disposition inline, header X-PDF-Source: generated|s3-cache.","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["invoices:read"]}},"/api/v1/invoices/{id}/send":{"post":{"operationId":"post_invoices_id_send","tags":["invoices"],"summary":"Send an invoice by email","description":"**Scope:** `invoices:write`\n\n**Response:** 200 { data: { invoiceId, invoiceNumber, status (lowercase), sentTo, cc, bcc, sentAt } } (also available as snake_case)\n\n- Sending a draft with an exhausted document quota returns 403 plan_limit_reached before a number is allocated.\n- Business logic: lib/invoice-api-email.ts. Drafts with a placeholder receive an issued number before sending (counter, invoice and INVOICE_NUMBER_ISSUED audit in one transaction). If delivery fails, the number stays on the draft and is reused on the next attempt; it is never returned to the sequence. PDF and email content match the dashboard.\n- Only after successful delivery: status DRAFT/FINAL → SENT, sentAt, document lock (lockedAt, v1). Audit INVOICE_SENT_VIA_API, webhook invoice.sent.\n- A cancelled invoice can be emailed and remains cancelled. Records sentAt if absent, an audit entry and webhook invoice.sent with the actual status.\n- A draft or final invoice is reserved while it is being sent, as in the dashboard. A concurrent second send of the same invoice (API, individual or bulk send) receives 409 send_in_progress without sending email or assigning a number. After a crash, the reservation expires in ten minutes.\n- Errors: 404 unknown invoice; 400 no recipient or unissued draft with a placeholder number; 409 converted proforma or send already in progress (send_in_progress); 500 delivery failed.\n- 409 converted proforma: { error, invoice_id, invoice_number } identifies the invoice to send. invoiceId/invoiceNumber (the same values) are deprecated and will be removed in a later version.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"recipientEmail":{"type":"string","description":"Recipient. Alias: `recipient_email`. Default: Customer's email address."},"cc":{"type":"string","description":"Copy."},"bcc":{"type":"string","description":"Blind copy."}}}}}},"responses":{"200":{"description":"200 { data: { invoiceId, invoiceNumber, status (lowercase), sentTo, cc, bcc, sentAt } } (also available as snake_case)","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"State conflict: incorrect status for the action, concurrent modification or number already in use.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"invoice_id":{"type":"string","description":"Converted proforma: ID of the resulting invoice."},"invoice_number":{"type":"string","description":"Converted proforma: number of the resulting invoice."},"invoiceId":{"type":"string","description":"Deprecated, same value as invoice_id. Will be removed in a later version.","deprecated":true},"invoiceNumber":{"type":"string","description":"Deprecated, same value as invoice_number. Will be removed in a later version.","deprecated":true}}}]},"example":{"error":"Proforma invoice has been converted. Send the invoice instead.","invoice_id":"cm2x7k9q80001abcd1234efgh","invoice_number":"RE-2026-0042","invoiceId":"cm2x7k9q80001abcd1234efgh","invoiceNumber":"RE-2026-0042"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["invoices:write"]}},"/api/v1/invoices/{id}/cancel":{"post":{"operationId":"post_invoices_id_cancel","tags":["invoices"],"summary":"Cancel an invoice, optionally creating a cancellation credit note","description":"**Scope:** `invoices:write`\n\n**Response:** 200 { data: { id, invoiceNumber, status: \"CANCELLED\", stornoInvoiceId, stornoInvoiceNumber, stornoEmailSent, [warning] } }\n\n- Uses cancelInvoiceWithOptionalStorno (lib/storno.ts). A corresponding credit note is created only for issued INVOICE documents (isStornoEligible): reason CANCELLATION, its own GS number, status FINAL and negative amounts, atomically with the status change.\n- Drafts, proformas and credit notes are only set to CANCELLED. Cancellation is allowed from DRAFT, FINAL, SENT or OVERDUE; otherwise 409. Concurrent status change → 409.\n- Payments transferred from a proforma are returned to that proforma on cancellation. If the proforma is missing or cancelled (legacy data), nothing is written: 409 instructs you to remove the payments first, as with PUT status CANCELLED.\n- Optional email delivery is non-fatal (→ warning); cancellation remains effective. Webhooks invoice.cancelled and credit_note.created, plus invoice.sent on delivery.\n- Audit INVOICE_CANCELLED_VIA_API (actor: the company's earliest OWNER).\n- 409 converted proforma: { error, invoice_id, invoice_number } identifies the invoice to cancel. invoiceId/invoiceNumber (the same values) are deprecated and will be removed in a later version.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"createStorno":{"type":"boolean","default":true,"description":"Create a cancellation credit note. Alias: `create_storno`."},"sendStornoToCustomer":{"type":"boolean","default":false,"description":"Email the cancellation credit note. Alias: `send_storno_to_customer`."},"recipientEmail":{"type":"string","description":"Only relevant when sending. Alias: `recipient_email`."}}}}}},"responses":{"200":{"description":"200 { data: { id, invoiceNumber, status: \"CANCELLED\", stornoInvoiceId, stornoInvoiceNumber, stornoEmailSent, [warning] } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"State conflict: incorrect status for the action, concurrent modification or number already in use.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"invoice_id":{"type":"string","description":"Converted proforma: ID of the resulting invoice."},"invoice_number":{"type":"string","description":"Converted proforma: number of the resulting invoice."},"invoiceId":{"type":"string","description":"Deprecated, same value as invoice_id. Will be removed in a later version.","deprecated":true},"invoiceNumber":{"type":"string","description":"Deprecated, same value as invoice_number. Will be removed in a later version.","deprecated":true}}}]},"example":{"error":"Proforma invoice has been converted into an invoice and cannot be cancelled. Cancel the invoice instead.","invoice_id":"cm2x7k9q80001abcd1234efgh","invoice_number":"RE-2026-0042","invoiceId":"cm2x7k9q80001abcd1234efgh","invoiceNumber":"RE-2026-0042"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["invoices:write"]}},"/api/v1/invoices/{id}/storno":{"post":{"operationId":"post_invoices_id_storno","tags":["invoices"],"summary":"Create a cancellation credit note for an already cancelled invoice","description":"**Scope:** `invoices:write`\n\n**Response:** 201 { data: { id, invoiceNumber, invoiceType: \"CREDIT_NOTE\", creditNoteReason: \"CANCELLATION\", status: \"FINAL\", parentInvoiceId, issueDate, currency, netTotal, taxTotal, grossTotal, stornoEmailSent, [warning] } }\n\n- Matches createRetroactiveStorno for invoices already CANCELLED but without a cancellation credit note. No status change.\n- 404 unknown invoice; 409 invoice not CANCELLED (reference to …/cancel) or a cancellation credit note already exists; 400 ineligible document, such as a proforma or draft.\n- Webhook credit_note.created, plus invoice.sent on delivery. Audit INVOICE_STORNO_CREATED_VIA_API.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"sendToCustomer":{"type":"boolean","default":false,"description":"Send the cancellation credit note. Alias: `send_to_customer`."},"recipientEmail":{"type":"string","description":"Recipient. Alias: `recipient_email`."}}}}}},"responses":{"201":{"description":"201 { data: { id, invoiceNumber, invoiceType: \"CREDIT_NOTE\", creditNoteReason: \"CANCELLATION\", status: \"FINAL\", parentInvoiceId, issueDate, currency, netTotal, taxTotal, grossTotal, stornoEmailSent, [warning] } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["invoices:write"]}},"/api/v1/invoices/{id}/duplicate":{"post":{"operationId":"post_invoices_id_duplicate","tags":["invoices"],"summary":"Duplicate an invoice as a new draft","description":"**Scope:** `invoices:write`\n\n**Response:** 201 { data: { id, invoiceNumber, invoiceType: \"INVOICE\", status: \"DRAFT\", sourceInvoiceId, customerId, documentCategoryId, issueDate, dueDate, currency, netTotal, taxTotal, grossTotal, createdAt } }\n\n- Uses lib/invoice-duplicate.ts. The copy is always a regular invoice draft with a placeholder number. Quantities and totals are copied as positive values, including when copying a credit note. The service period is not copied.\n- 404 Invoice/Customer not found; 403 plan_limit_reached when the plan's document quota is reached (the copy counts as a new document). Audit INVOICE_DUPLICATED_VIA_API. No webhook, matching the dashboard.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"customerId":{"type":"string","description":"A different customer from the original. Alias: `customer_id`."},"adjustDates":{"type":"boolean","default":true,"description":"Invoice date becomes today; the payment term in days stays the same. Alias: `adjust_dates`."}}}}}},"responses":{"201":{"description":"201 { data: { id, invoiceNumber, invoiceType: \"INVOICE\", status: \"DRAFT\", sourceInvoiceId, customerId, documentCategoryId, issueDate, dueDate, currency, netTotal, taxTotal, grossTotal, createdAt } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["invoices:write"]}},"/api/v1/invoices/{id}/convert":{"post":{"operationId":"post_invoices_id_convert","tags":["invoices"],"summary":"Convert a proforma to a regular invoice","description":"**Scope:** `invoices:write`\n\n**Response:** 201 { data: { id, invoiceNumber, invoiceType: \"INVOICE\", status: \"DRAFT\", proformaInvoiceId, proformaInvoiceNumber, customerId, documentCategoryId, issueDate, dueDate, currency, netTotal, taxTotal, grossTotal, createdAt } }\n\n- No body. Uses convertProformaToInvoiceCore (lib/proforma-conversion.ts). The proforma keeps its number, document type and status, and is linked through metadata.convertedToInvoiceId, with metadata.convertedFromProformaId on the invoice.\n- Opening and closing text come from the company's invoice templates, not the proforma. Conversion is allowed from DRAFT, FINAL, SENT or OVERDUE.\n- 404 unknown document; 400 not a proforma; 403 plan_limit_reached at the document quota; 409 already converted (identifies the invoice) or status no longer convertible.\n- Audit CONVERT_TO_INVOICE on the proforma and CREATE_FROM_PROFORMA on the invoice. Webhook invoice.created.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"responses":{"201":{"description":"201 { data: { id, invoiceNumber, invoiceType: \"INVOICE\", status: \"DRAFT\", proformaInvoiceId, proformaInvoiceNumber, customerId, documentCategoryId, issueDate, dueDate, currency, netTotal, taxTotal, grossTotal, createdAt } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["invoices:write"]}},"/api/v1/invoices/{id}/payments":{"get":{"operationId":"get_invoices_id_payments","tags":["invoices"],"summary":"List an invoice's payments","description":"**Scope:** `invoices:read`\n\n**Response:** { data: { invoice_id, invoice_number, gross_total, total_paid, amount_due, currency, payments: [{ id, amount, payment_date/paymentDate, payment_method/paymentMethod, reference, notes, created_at/createdAt }] } }, payments sorted by date descending.\n\n- 404 Invoice not found for an unknown invoice or one from another company.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"{ data: { invoice_id, invoice_number, gross_total, total_paid, amount_due, currency, payments: [{ id, amount, payment_date/paymentDate, payment_method/paymentMethod, reference, notes, created_at/createdAt }] } }, payments sorted by date descending.","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["invoices:read"]},"post":{"operationId":"post_invoices_id_payments","tags":["invoices"],"summary":"Record an invoice payment","description":"**Scope:** `invoices:write`\n\n**Response:** 201 { data: { …payment, invoice_id/invoiceId, invoice_number/invoiceNumber, invoice_status/invoiceStatus (lowercase), total_paid/totalPaid, amount_due/amountDue } }\n\n- When paid in full, the invoice becomes PAID with paidAt set to the payment date and webhook invoice.paid is triggered. Audit PAYMENT_RECORDED_VIA_API.\n- 400 amount missing/≤0; 400 invalid payment_method (with allowedValues); 400 invalid payment_date; 400 (code invoice_status) unless the invoice is FINAL/SENT/OVERDUE; 409 (code amount_exceeds_remaining) above the outstanding balance + €0.01; 404 unknown invoice or another company's invoice.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"amount":{"type":"number","description":"Amount, must be > 0."},"payment_method":{"type":"string","enum":["BANK_TRANSFER","CASH","CREDIT_CARD","PAYPAL","OTHER"],"default":"BANK_TRANSFER","description":"Case and hyphens are ignored. Alias: `paymentMethod`."},"payment_date":{"type":"string","format":"date","description":"Payment date. Alias: `paymentDate`. Default: now."},"reference":{"type":"string","description":"For example, a transaction ID."},"notes":{"type":"string","description":"Free text."}},"required":["amount"]}}}},"responses":{"201":{"description":"201 { data: { …payment, invoice_id/invoiceId, invoice_number/invoiceNumber, invoice_status/invoiceStatus (lowercase), total_paid/totalPaid, amount_due/amountDue } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["invoices:write"]}},"/api/v1/invoices/{id}/payments/{paymentId}":{"put":{"operationId":"put_invoices_id_payments_paymentId","tags":["invoices"],"summary":"Update a payment","description":"**Scope:** `invoices:write`\n\n**Response:** 200 { data: { …payment, invoice_id/invoiceId, invoice_number/invoiceNumber, invoice_status/invoiceStatus, total_paid/totalPaid, amount_due/amountDue } }\n\n- Status update: paid in full → PAID + paidAt + webhook invoice.paid; outstanding again → SENT, paidAt cleared. Audit PAYMENT_UPDATED_VIA_API.\n- 404 Payment not found, including for another invoice/company; 400 amount ≤0, invalid payment method or date; 409 (code invoice_cancelled) for a cancelled invoice; 409 (code amount_exceeds_remaining) above the balance excluding this payment.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}},{"name":"paymentId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"amount":{"type":"number","description":"> 0, default is the previous amount."},"payment_method":{"type":"string","enum":["BANK_TRANSFER","CASH","CREDIT_CARD","PAYPAL","OTHER"],"description":"As for POST. Alias: `paymentMethod`. Default: previous payment method."},"payment_date":{"type":"string","format":"date","description":"Alias: `paymentDate`. Default: previous date."},"reference":{"type":"string","nullable":true},"notes":{"type":"string","nullable":true}}}}}},"responses":{"200":{"description":"200 { data: { …payment, invoice_id/invoiceId, invoice_number/invoiceNumber, invoice_status/invoiceStatus, total_paid/totalPaid, amount_due/amountDue } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["invoices:write"]},"delete":{"operationId":"delete_invoices_id_payments_paymentId","tags":["invoices"],"summary":"Delete a payment","description":"**Scope:** `invoices:write`\n\n**Response:** 200 { data: { id, deleted: true, invoice_id/invoiceId, invoice_number/invoiceNumber, invoice_status/invoiceStatus, total_paid/totalPaid, amount_due/amountDue } }\n\n- If the invoice was PAID and has a balance again after deletion, it returns to SENT and paidAt is cleared. Deliberately allowed on cancelled invoices to remove incorrect postings.\n- Audit PAYMENT_DELETED_VIA_API. 404 Payment not found.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}},{"name":"paymentId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"200 { data: { id, deleted: true, invoice_id/invoiceId, invoice_number/invoiceNumber, invoice_status/invoiceStatus, total_paid/totalPaid, amount_due/amountDue } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["invoices:write"]}},"/api/v1/invoices/{id}/dunning":{"get":{"operationId":"get_invoices_id_dunning","tags":["invoices"],"summary":"Read reminder status and history","description":"**Scope:** `invoices:read`\n\n**Response:** { data: { invoice_id, invoice_number, status, due_date, days_overdue, gross_total, customer, dunning: { is_paused, paused_at, paused_reason, current_level, next_level/nextLevel, max_level_reached, total_reminders, total_fees, total_interest, total_outstanding }, reminders: [...] } }\n\n- next_level/nextLevel/max_level_reached are new; next_level is the stage sent next by action: \"send\" (stage 3 stays at stage 3).","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"{ data: { invoice_id, invoice_number, status, due_date, days_overdue, gross_total, customer, dunning: { is_paused, paused_at, paused_reason, current_level, next_level/nextLevel, max_level_reached, total_reminders, total_fees, total_interest, total_outstanding }, reminders: [...] } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["invoices:read"]},"post":{"operationId":"post_invoices_id_dunning","tags":["invoices"],"summary":"Pause or resume reminders, or send a reminder stage","description":"**Scope:** `invoices:write`\n\n**Response:** pause/resume: 200 { data: { invoice_id, invoice_number, dunning_paused, dunning_paused_at, dunning_paused_reason } }. send: 201 { data: { id, invoice_id/invoiceId, level, sent_at/sentAt, sent_to/sentTo, fee, interest_rate/interestRate, interest_amount/interestAmount, total_amount/totalAmount, days_overdue/daysOverdue, remaining_amount/remainingAmount } }\n\n- pause/resume: sets dunningPaused/-At/-Reason, audit DUNNING_PAUSED_VIA_API / DUNNING_RESUMED_VIA_API.\n- send: sends the branded reminder email (stage 1: payment reminder, 2: first formal reminder, 3: final reminder), creates a Reminder row, changes SENT → OVERDUE (webhook invoice.overdue), and triggers reminder.sent plus reminder.escalated from stage 2. Audit REMINDER_SENT_VIA_API.\n- 400 invalid action/field types; 400 (code invoice_status) unless SENT/OVERDUE; 400 (code no_recipient) without a customer email; 409 dunning_paused/max_level_reached/already_paid; 502 (code email_failed) on failed delivery, with no Reminder row.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"action":{"type":"string","enum":["pause","resume","send"],"description":"pause | resume | send."},"reason":{"type":"string","description":"Stored only for pause."},"recipient_email":{"type":"string","description":"Only for send. Alias: `recipientEmail`. Default: Customer's email address."},"custom_reminder_fee":{"type":"number","description":"Only for send, ≥ 0. Alias: `customReminderFee`. Default: Company.reminderFee1/2/3 by stage."},"include_interest":{"type":"boolean","default":false,"description":"Only for send; rate from Company.defaultInterestRate, simple interest: outstanding amount × rate × days/365. Alias: `includeInterest`."}},"required":["action"]}}}},"responses":{"201":{"description":"pause/resume: 200 { data: { invoice_id, invoice_number, dunning_paused, dunning_paused_at, dunning_paused_reason } }. send: 201 { data: { id, invoice_id/invoiceId, level, sent_at/sentAt, sent_to/sentTo, fee, interest_rate/interestRate, interest_amount/interestAmount, total_amount/totalAmount, days_overdue/daysOverdue, remaining_amount/remainingAmount } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["invoices:write"]}},"/api/v1/invoices/{id}/correct":{"post":{"operationId":"post_invoices_id_correct","tags":["invoices"],"summary":"Create a correction under § 31 Abs. 5 UStDV as a new version","description":"**Scope:** `invoices:write`\n\n**Response:** 200 { data: { invoiceId, invoiceNumber, versionNumber, changeReason, lazyInitialVersion } }\n\n- Keeps the invoice number and creates a DocumentVersion (CORRECTION) with a full snapshot. Recalculates taxes and totals, regenerates and archives the PDF. For legacy invoices without lockedAt, v1 is created retrospectively (lazyInitialVersion: true).\n- Audit DOCUMENT_CORRECTED, webhook invoice.updated. 400 missing/short change_reason; 400 (code not_locked), use PUT instead; 400 invalid enum/date/number or empty lines; 404 unknown invoice/customer or another company's record.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"change_reason":{"type":"string","description":"At least MIN_CHANGE_REASON_LENGTH characters. Alias: `changeReason`."},"customer_id":{"type":"string","description":"Change customer. Alias: `customerId`."},"issue_date":{"type":"string","format":"date"},"due_date":{"type":"string","format":"date"},"service_period_start":{"type":"string","format":"date"},"service_period_end":{"type":"string","format":"date"},"reverse_charge":{"type":"boolean"},"tax_exemption_reason":{"type":"string","enum":["NONE","SMALL_BUSINESS","REVERSE_CHARGE_EU","REVERSE_CHARGE_EU_NO_VAT","THIRD_COUNTRY","INTRA_COMMUNITY","EXPORT_DELIVERY","REVERSE_CHARGE_13B","INTERNAL_SUPPLY","OTHER_EXEMPTION"]},"tax_exemption_note":{"type":"string"},"tax_country":{"type":"string"},"delivery_country":{"type":"string"},"oss_country":{"type":"string"},"oss_applicable":{"type":"boolean"},"header_text":{"type":"string"},"notes":{"type":"string"},"footer":{"type":"string"},"order_number":{"type":"string"},"buyer_reference":{"type":"string"},"lines":{"type":"array","items":{"type":"object"},"description":"Replaces all line items; each item accepts description (required), quantity (default 1), unit_price (default 0), tax_rate (default 0), discount (absolute, default 0), line_type (default PRODUCT) Allowed values: PRODUCT, SERVICE, DISCOUNT_PERCENT, DISCOUNT_ABSOLUTE, SHIPPING, ADVANCE_PAYMENT, CREDIT, TEXT, SUBTOTAL, PAGEBREAK."}},"required":["change_reason"]}}}},"responses":{"200":{"description":"200 { data: { invoiceId, invoiceNumber, versionNumber, changeReason, lazyInitialVersion } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["invoices:write"]}},"/api/v1/invoices/{id}/versions":{"get":{"operationId":"get_invoices_id_versions","tags":["invoices"],"summary":"Read an invoice's version history","description":"**Scope:** `invoices:read`\n\n**Response:** { data: { invoiceId, currentVersion, versions: [{ id, versionNumber, changeType, changeReason, hasPdf, createdAt, createdBy }] } }, sorted by versionNumber descending.\n\n- Same fields as dashboard route /api/documents/invoice/{id}/versions. 404 Invoice not found.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"{ data: { invoiceId, currentVersion, versions: [{ id, versionNumber, changeType, changeReason, hasPdf, createdAt, createdBy }] } }, sorted by versionNumber descending.","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["invoices:read"]}},"/api/v1/invoices/{id}/versions/{version}/pdf":{"get":{"operationId":"get_invoices_id_versions_version_pdf","tags":["invoices"],"summary":"Download the archived PDF of a specific version","description":"**Scope:** `invoices:read`\n\n**Response:** 200 PDF bytes, Content-Disposition inline; filename=\"{document number}-v{n}.pdf\", Cache-Control private, max-age=3600, immutable.\n\n- Returns only the archived PDF, never a newly rendered file (GoBD). Audit DOCUMENT_VERSION_PDF_DOWNLOADED.\n- 400 Invalid version number; 404 Invoice/Version not found or the version's PDF is unavailable or missing from storage.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}},{"name":"version","in":"path","required":true,"schema":{"type":"string"}},{"name":"version","in":"query","required":true,"description":"Version number ≥ 1.","schema":{"type":"number"}}],"responses":{"200":{"description":"200 PDF bytes, Content-Disposition inline; filename=\"{document number}-v{n}.pdf\", Cache-Control private, max-age=3600, immutable.","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["invoices:read"]}},"/api/v1/invoices/{id}/facturx":{"get":{"operationId":"get_invoices_id_facturx","tags":["invoices"],"summary":"Download Factur-X/ZUGFeRD XML","description":"**Scope:** `invoices:read`\n\n**Response:** 200 XML, Content-Type application/xml; charset=utf-8, Content-Disposition attachment.\n\n- Factur-X/ZUGFeRD 2.3 (EN 16931, CII, BASIC profile), matching the dashboard download. Drafts, credit notes (TypeCode 381) and proformas (325) return XML; proformas already converted to invoices return 409 with a reference to the invoice.\n- Internal notes are excluded; tax notices and closing text are included. Audit EINVOICE_XML_DOWNLOADED_VIA_API. 404 Invoice not found.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"200 XML, Content-Type application/xml; charset=utf-8, Content-Disposition attachment.","content":{"application/xml":{"schema":{"type":"string","format":"binary"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["invoices:read"]}},"/api/v1/credit-notes":{"get":{"operationId":"get_credit_notes","tags":["credit-notes"],"summary":"List credit notes","description":"**Scope:** `invoices:read`\n\n**Response:** { data: CreditNoteListItem[], pagination: { total, limit, offset, hasMore, has_more } }\n\n- payout_status is filtered in the database query rather than after pagination, so total/has_more match the result.\n- Invalid status/payout_status → 400 with allowedValues, previously Prisma returned 500. Sorted by issueDate descending.","parameters":[{"name":"limit","in":"query","required":false,"description":"Page size.","schema":{"type":"number","default":50}},{"name":"offset","in":"query","required":false,"description":"Offset.","schema":{"type":"number","default":0}},{"name":"recipient_id","in":"query","required":false,"description":"Filter by recipient.","schema":{"type":"string"}},{"name":"customer_id","in":"query","required":false,"description":"Filter by recipient.","schema":{"type":"string"}},{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["DRAFT","FINAL","SENT","PAID","OVERDUE","CANCELLED"]}},{"name":"payout_status","in":"query","required":false,"description":"Filters in the query (payout.is.status)","schema":{"type":"string","enum":["PENDING","APPROVED","EXECUTED","FAILED","CANCELLED"]}},{"name":"category_id","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"{ data: CreditNoteListItem[], pagination: { total, limit, offset, hasMore, has_more } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/CreditNoteListItem"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["invoices:read"]},"post":{"operationId":"post_credit_notes","tags":["credit-notes"],"summary":"Create a standalone credit note or one linked to an invoice","description":"**Scope:** `invoices:write`\n\n**Response:** 201 { data: { id, invoice_number/invoiceNumber, numberIssued, credit_note_reason/creditNoteReason, status, issue_date, recipient, currency, net_total, tax_total, gross_total, lines[], notes, footer, metadata, parent_invoice_id, payout, created_at } }\n\n- New credit notes are always drafts with an ENTWURF-… placeholder number. The actual GS number is assigned on issue: PUT status change, POST …/send or POST /credit-notes/{id}/execute.\n- Linked mode (parent_invoice_id without lines) matches createCreditNote: only for SENT/PAID/OVERDUE invoices, otherwise 409. Tax details and document category come from the source invoice.\n- Amounts and quantities are stored as negative values, matching the dashboard; payout.amount is positive.\n- 404 missing source invoice/recipient or another company's record. Audit CREDIT_NOTE_CREATED_VIA_API, webhook credit_note.created.","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"documentLocale":{"type":"string","enum":["de","en"],"description":"On creation only: explicit document language takes precedence over customer/company defaults, then German. Derived documents retain the source language. The response contains documentLocale and documentTextVersion; editing does not change the stored profile. Alias: `document_locale`."},"recipient_id":{"type":"string","description":"Required unless parent_invoice_id is set. Alias: `recipientId`."},"reason":{"type":"string","enum":["PAYOUT","COMMISSION","GOODWILL","RETURN","PRICE_CORRECTION","OTHER"],"default":"PAYOUT","description":"CANCELLATION is not allowed (→ 400)"},"lines":{"type":"array","items":{"type":"object"},"description":"Required except in linked mode (parent_invoice_id without lines)"},"parent_invoice_id":{"type":"string","description":"Linked mode. Alias: `parentInvoiceId`."},"partial_lines":{"type":"array","items":{"type":"object","properties":{"line_id":{},"quantity":{}}},"description":"Only in linked mode without lines; otherwise all line items are credited. Alias: `partialLines`."},"issue_date":{"type":"string","format":"date","description":"Alias: `issueDate`. Default: today."},"currency":{"type":"string","description":"Default: Currency of the source invoice, or EUR."},"reverse_charge":{"type":"boolean","description":"Alias: `reverseCharge`."},"tax_exemption_reason":{"type":"string","enum":["NONE","SMALL_BUSINESS","REVERSE_CHARGE_EU","REVERSE_CHARGE_EU_NO_VAT","THIRD_COUNTRY","INTRA_COMMUNITY","EXPORT_DELIVERY","REVERSE_CHARGE_13B","INTERNAL_SUPPLY","OTHER_EXEMPTION"],"description":"Alias: `taxExemptionReason`."},"notes":{"type":"string","description":"Stored unchanged; no text is generated from the reason."},"footer":{"type":"string","nullable":true,"description":"Missing/null → notes is also used as closing text; an empty string deliberately leaves it blank."},"metadata":{"type":"object"},"create_payout":{"type":"boolean","default":false,"description":"Alias: `createPayout`."},"payout_iban":{"type":"string","description":"No IBAN in either body or customer → 400."},"payout_bic":{"type":"string","description":"No IBAN in either body or customer → 400."},"payout_account_holder":{"type":"string","description":"No IBAN in either body or customer → 400."},"payout_bank_name":{"type":"string","description":"No IBAN in either body or customer → 400."},"payout_reference":{"type":"string","description":"No IBAN in either body or customer → 400."}}}}}},"responses":{"201":{"description":"201 { data: { id, invoice_number/invoiceNumber, numberIssued, credit_note_reason/creditNoteReason, status, issue_date, recipient, currency, net_total, tax_total, gross_total, lines[], notes, footer, metadata, parent_invoice_id, payout, created_at } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["invoices:write"]}},"/api/v1/credit-notes/{id}":{"get":{"operationId":"get_credit_notes_id","tags":["credit-notes"],"summary":"Read credit note details","description":"**Scope:** `invoices:read`\n\n**Response:** { data: { id, invoice_number/invoiceNumber, numberIssued, credit_note_reason/creditNoteReason, status, issue_date, due_date, recipient (with bank details), currency, net_total, tax_total, gross_total, reverse_charge, tax_exemption_reason, lines[], notes, footer, metadata, parent_invoice, payout, created_at, updated_at } }\n\n- 404 for a missing credit note or one from another company.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"{ data: { id, invoice_number/invoiceNumber, numberIssued, credit_note_reason/creditNoteReason, status, issue_date, due_date, recipient (with bank details), currency, net_total, tax_total, gross_total, reverse_charge, tax_exemption_reason, lines[], notes, footer, metadata, parent_invoice, payout, created_at, updated_at } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["invoices:read"]},"put":{"operationId":"put_credit_notes_id","tags":["credit-notes"],"summary":"Update a credit note draft","description":"**Scope:** `invoices:write`\n\n**Response:** { data: { id, invoice_number/invoiceNumber, numberIssued, credit_note_reason/creditNoteReason, status, currency, issue_date, net_total, tax_total, gross_total, lines[], notes, footer, metadata, updated_at } }\n\n- DRAFT only, otherwise 400. Line replacement and totals run in one transaction.\n- Audit CREDIT_NOTE_UPDATED_VIA_API.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"notes":{"type":"string"},"footer":{"type":"string","nullable":true},"metadata":{"type":"object"},"reason":{"type":"string","enum":["PAYOUT","COMMISSION","GOODWILL","RETURN","PRICE_CORRECTION","OTHER"],"description":"Excludes CANCELLATION."},"issue_date":{"type":"string","format":"date","description":"Alias: `issueDate`."},"currency":{"type":"string"},"reverse_charge":{"type":"boolean","description":"Alias: `reverseCharge`."},"tax_exemption_reason":{"type":"string","enum":["NONE","SMALL_BUSINESS","REVERSE_CHARGE_EU","REVERSE_CHARGE_EU_NO_VAT","THIRD_COUNTRY","INTRA_COMMUNITY","EXPORT_DELIVERY","REVERSE_CHARGE_13B","INTERNAL_SUPPLY","OTHER_EXEMPTION"],"description":"Alias: `taxExemptionReason`."},"lines":{"type":"array","items":{"type":"object"},"description":"Replaces line items; totals are recalculated through calculateDocumentTotals as negative values."}}}}}},"responses":{"200":{"description":"{ data: { id, invoice_number/invoiceNumber, numberIssued, credit_note_reason/creditNoteReason, status, currency, issue_date, net_total, tax_total, gross_total, lines[], notes, footer, metadata, updated_at } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["invoices:write"]}},"/api/v1/credit-notes/{id}/execute":{"post":{"operationId":"post_credit_notes_id_execute","tags":["credit-notes"],"summary":"Execute a payout (credit note → PAID)","description":"**Scope:** `invoices:write`\n\n**Response:** 200 { data: { id, credit_note_id, credit_note_number, status, amount, currency, reference, iban, bic, transaction_id, executed_at, credit_note_status } }\n\n- Sets the payout to EXECUTED and the credit note to PAID. If the credit note still has a placeholder number, the actual GS number is assigned in the transaction; a placeholder payout.reference is replaced with the GS number.\n- 400 no payout/already executed/cancelled; 409 concurrent issue. Audit PAYOUT_EXECUTED_VIA_API, webhooks payout.executed + credit_note.paid.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"transaction_id":{"type":"string","description":"Alias: `transactionId`."},"executed_at":{"type":"string","format":"date","description":"Alias: `executedAt`. Default: now."},"notes":{"type":"string","description":"Appended to payout.description."}}}}}},"responses":{"200":{"description":"200 { data: { id, credit_note_id, credit_note_number, status, amount, currency, reference, iban, bic, transaction_id, executed_at, credit_note_status } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["invoices:write"]},"put":{"operationId":"put_credit_notes_id_execute","tags":["credit-notes"],"summary":"Approve, cancel or mark a payout as failed","description":"**Scope:** `invoices:write`\n\n**Response:** { data: { id, credit_note_id, status, amount, approved_at, approved_by, failure_reason } }\n\n- cancel sets the credit note to CANCELLED; a draft deliberately receives no document number.\n- Audit PAYOUT_APPROVE|CANCEL|FAIL_VIA_API, Webhooks payout.approved / payout.cancelled / payout.failed.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"action":{"type":"string","enum":["approve","cancel","fail"],"description":"approve only from PENDING; fail only from APPROVED; cancel not from EXECUTED."},"failure_reason":{"type":"string","description":"Alias: `failureReason`."},"approved_by":{"type":"string","description":"Alias: `approvedBy`. Default: \"API\"."}},"required":["action"]}}}},"responses":{"200":{"description":"{ data: { id, credit_note_id, status, amount, approved_at, approved_by, failure_reason } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["invoices:write"]}},"/api/v1/customers":{"get":{"operationId":"get_customers","tags":["customers"],"summary":"List customers","description":"**Scope:** `customers:read`\n\n**Response:** { data: Customer[], pagination }\n\n- Archived customers are excluded unless include_archived/archived is set, matching the dashboard customer list. Sorted by name ascending.","parameters":[{"name":"limit","in":"query","required":false,"description":"1–100.","schema":{"type":"number","default":50}},{"name":"offset","in":"query","required":false,"schema":{"type":"number","default":0}},{"name":"search","in":"query","required":false,"description":"Case-insensitive match on name, email or customerNumber.","schema":{"type":"string"}},{"name":"include_archived","in":"query","required":false,"description":"Include archived customers. Alias: `includeArchived`.","schema":{"type":"boolean"}},{"name":"archived","in":"query","required":false,"description":"true = archived only, false = active only.","schema":{"type":"boolean"}}],"responses":{"200":{"description":"{ data: Customer[], pagination }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Customer"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["customers:read"]},"post":{"operationId":"post_customers","tags":["customers"],"summary":"Create a customer","description":"**Scope:** `customers:write`\n\n**Response:** 201 { data: Customer }\n\n- Plan gate checkCustomerLimit → 403 { code: \"plan_limit_reached\", limit, current }.\n- Duplicate email returns 409 { customerId } identifying the oldest existing customer, including archived customers. The entire address is compared case-insensitively; _ and % are literal characters. Concurrent POST requests for the same address also create exactly one customer. An occupied customerNumber returns 409 { code: \"CUSTOMER_NUMBER_TAKEN\" }; an exhausted number range returns 409 { code: \"CUSTOMER_NUMBER_UNAVAILABLE\" }.\n- taxIdValidated is never accepted from input. It is set only if the same company successfully validated the same VAT ID through VIES within the last hour.\n- Audit CUSTOMER_CREATED_VIA_API, Webhook customer.created.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"Not empty."},"customerNumber":{"type":"string","description":"Empty/missing = automatic allocation (lib/customer-number.ts)"},"salutation":{"type":"string"},"title":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"department":{"type":"string"},"email":{"type":"string"},"phone":{"type":"string"},"mobile":{"type":"string"},"website":{"type":"string"},"taxId":{"type":"string"},"taxNumber":{"type":"string"},"registryCourt":{"type":"string"},"registryNumber":{"type":"string"},"bmdAccountNumber":{"type":"string","description":"Digits only, at most nine."},"bmdRevenueAccount":{"type":"string","description":"Digits only, at most nine."},"datevId":{"type":"string"},"documentLocale":{"type":"string","nullable":true,"enum":["de","en"],"description":"Language for new documents; null inherits the company setting. document_locale is also accepted. Existing documents and recurring schedules remain unchanged."},"defaultTaxExemptionReason":{"type":"string","nullable":true,"enum":["NONE","SMALL_BUSINESS","REVERSE_CHARGE_EU","REVERSE_CHARGE_EU_NO_VAT","THIRD_COUNTRY","INTRA_COMMUNITY","EXPORT_DELIVERY","REVERSE_CHARGE_13B","INTERNAL_SUPPLY","OTHER_EXEMPTION"]},"defaultTaxExemptionNote":{"type":"string"},"street":{"type":"string","description":"Billing address."},"zipCode":{"type":"string","description":"Billing address."},"city":{"type":"string","description":"Billing address."},"state":{"type":"string","description":"Billing address."},"countryCode":{"type":"string","description":"Billing address."},"country":{"type":"string","description":"An empty value makes no change. Default: Österreich."},"shippingName":{"type":"string","description":"Delivery address."},"shippingCompany":{"type":"string","description":"Delivery address."},"shippingStreet":{"type":"string","description":"Delivery address."},"shippingZipCode":{"type":"string","description":"Delivery address."},"shippingCity":{"type":"string","description":"Delivery address."},"shippingState":{"type":"string","description":"Delivery address."},"shippingCountry":{"type":"string","description":"Delivery address."},"shippingCountryCode":{"type":"string","description":"Delivery address."},"bankAccountHolder":{"type":"string"},"bankName":{"type":"string"},"iban":{"type":"string"},"bic":{"type":"string"},"sepaMandateReference":{"type":"string"},"sepaMandateDate":{"type":"string","format":"date"},"sepaMandateType":{"type":"string","enum":["CORE","B2B"]},"sepaMandateStatus":{"type":"string","enum":["ACTIVE","REVOKED","EXPIRED"]},"sepaSequenceType":{"type":"string","enum":["FRST","RCUR","OOFF","FNAL"]},"paymentTermDays":{"type":"number","description":"≥ 0."},"discountPercent":{"type":"number","description":"0–100."},"discountDays":{"type":"number","description":"≥ 0."},"notes":{"type":"string"},"category":{"type":"string"},"contacts":{"description":"null clears the field."},"billingPrefs":{"description":"null clears the field."},"metadata":{"description":"null clears the field."},"address":{"type":"string","description":"Free text; derived from structured fields if any are supplied."},"shippingAddress":{"type":"string","description":"Free text; derived from structured fields if any are supplied."}},"required":["name"]}}}},"responses":{"201":{"description":"201 { data: Customer }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Customer"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["customers:write"]}},"/api/v1/customers/{id}":{"get":{"operationId":"get_customers_id","tags":["customers"],"summary":"Read customer details","description":"**Scope:** `customers:read`\n\n**Response:** { data: Customer & { invoiceCount, recentInvoices: [...] } } (ten most recent documents)\n\n- 404 for a missing customer or one from another company.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"{ data: Customer & { invoiceCount, recentInvoices: [...] } } (ten most recent documents)","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Customer"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["customers:read"]},"put":{"operationId":"put_customers_id","tags":["customers"],"summary":"Update a customer","description":"**Scope:** `customers:write`\n\n**Response:** { data: Customer }\n\n- Behaviour change: the route previously wrote only six fields (name, email, taxId, address, shippingAddress, contacts) and discarded the rest. It now supports the full form.\n- Changing an email to one already used by another customer (case-insensitive) returns 409 { customerId }, checked under the same lock as customer creation. An occupied customer number returns 409 { code: \"CUSTOMER_NUMBER_TAKEN\" }. Changing taxId resets taxIdValidated, or adopts a VIES check no more than one hour old.\n- Audit CUSTOMER_UPDATED_VIA_API, Webhook customer.updated.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"Not empty."},"customerNumber":{"type":"string","description":"Empty/missing = automatic allocation (lib/customer-number.ts)"},"salutation":{"type":"string"},"title":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"department":{"type":"string"},"email":{"type":"string"},"phone":{"type":"string"},"mobile":{"type":"string"},"website":{"type":"string"},"taxId":{"type":"string"},"taxNumber":{"type":"string"},"registryCourt":{"type":"string"},"registryNumber":{"type":"string"},"bmdAccountNumber":{"type":"string","description":"Digits only, at most nine."},"bmdRevenueAccount":{"type":"string","description":"Digits only, at most nine."},"datevId":{"type":"string"},"documentLocale":{"type":"string","nullable":true,"enum":["de","en"],"description":"Language for new documents; null inherits the company setting. document_locale is also accepted. Existing documents and recurring schedules remain unchanged."},"defaultTaxExemptionReason":{"type":"string","nullable":true,"enum":["NONE","SMALL_BUSINESS","REVERSE_CHARGE_EU","REVERSE_CHARGE_EU_NO_VAT","THIRD_COUNTRY","INTRA_COMMUNITY","EXPORT_DELIVERY","REVERSE_CHARGE_13B","INTERNAL_SUPPLY","OTHER_EXEMPTION"]},"defaultTaxExemptionNote":{"type":"string"},"street":{"type":"string","description":"Billing address."},"zipCode":{"type":"string","description":"Billing address."},"city":{"type":"string","description":"Billing address."},"state":{"type":"string","description":"Billing address."},"countryCode":{"type":"string","description":"Billing address."},"country":{"type":"string","description":"An empty value makes no change. Default: Österreich."},"shippingName":{"type":"string","description":"Delivery address."},"shippingCompany":{"type":"string","description":"Delivery address."},"shippingStreet":{"type":"string","description":"Delivery address."},"shippingZipCode":{"type":"string","description":"Delivery address."},"shippingCity":{"type":"string","description":"Delivery address."},"shippingState":{"type":"string","description":"Delivery address."},"shippingCountry":{"type":"string","description":"Delivery address."},"shippingCountryCode":{"type":"string","description":"Delivery address."},"bankAccountHolder":{"type":"string"},"bankName":{"type":"string"},"iban":{"type":"string"},"bic":{"type":"string"},"sepaMandateReference":{"type":"string"},"sepaMandateDate":{"type":"string","format":"date"},"sepaMandateType":{"type":"string","enum":["CORE","B2B"]},"sepaMandateStatus":{"type":"string","enum":["ACTIVE","REVOKED","EXPIRED"]},"sepaSequenceType":{"type":"string","enum":["FRST","RCUR","OOFF","FNAL"]},"paymentTermDays":{"type":"number","description":"≥ 0."},"discountPercent":{"type":"number","description":"0–100."},"discountDays":{"type":"number","description":"≥ 0."},"notes":{"type":"string"},"category":{"type":"string"},"contacts":{"description":"null clears the field."},"billingPrefs":{"description":"null clears the field."},"metadata":{"description":"null clears the field."},"address":{"type":"string","description":"Free text; derived from structured fields if any are supplied."},"shippingAddress":{"type":"string","description":"Free text; derived from structured fields if any are supplied."}},"description":"All POST fields: All optional, with PATCH semantics: only supplied fields are written. customerNumber: \"\"/null removes the number without allocating a new one."}}}},"responses":{"200":{"description":"{ data: Customer }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Customer"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["customers:write"]},"delete":{"operationId":"delete_customers_id","tags":["customers"],"summary":"Delete a customer","description":"**Scope:** `customers:write`\n\n**Response:** { success: true }\n\n- Unused customers only (lib/customer-lifecycle.ts). Any reference → 409 { code: \"CUSTOMER_IN_USE\" }; archiving is recommended instead.\n- Webhook customer.deleted.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"{ success: true }","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["customers:write"]}},"/api/v1/customers/{id}/archive":{"post":{"operationId":"post_customers_id_archive","tags":["customers"],"summary":"Archive a customer","description":"**Scope:** `customers:write`\n\n**Response:** 200 { data: Customer }\n\n- Body is ignored. Sets isArchived=true and archivedAt=now. Audit CUSTOMER_ARCHIVED_VIA_API, webhook customer.updated. 404 for another company's customer.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"200 { data: Customer }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Customer"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["customers:write"]}},"/api/v1/customers/{id}/unarchive":{"post":{"operationId":"post_customers_id_unarchive","tags":["customers"],"summary":"Restore a customer from the archive","description":"**Scope:** `customers:write`\n\n**Response:** 200 { data: Customer }\n\n- Body is ignored. Sets isArchived=false and archivedAt=null. Audit CUSTOMER_UNARCHIVED_VIA_API, webhook customer.updated.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"200 { data: Customer }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Customer"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["customers:write"]}},"/api/v1/customers/{id}/validate-vat":{"post":{"operationId":"post_customers_id_validate_vat","tags":["customers"],"summary":"Validate a VAT ID through VIES and save the result on the customer","description":"**Scope:** `customers:write`\n\n**Response:** 200 { data: { valid, unavailable, countryCode, vatNumber, requestDate, name, address, errorMessage, customer: Customer } }\n\n- Calls VIES (lib/vies.ts), writes a VatValidation row and saves the normalised taxId, taxIdValidated and taxIdValidatedAt on the customer.\n- No VAT ID → 400. Audit CUSTOMER_VAT_VALIDATED_VIA_API, webhook customer.updated. The result can be used for a subsequent POST/PUT with the same VAT ID for one hour.\n- unavailable=true means VIES could not be reached because of timeout, maintenance or network failure. In that case, valid=false says nothing about the VAT ID. The customer's stored result remains unchanged if it belongs to the checked VAT ID.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"vatNumber":{"type":"string","description":"Alias: `vat_number`."},"taxId":{"type":"string","description":"If both are omitted, checks the stored VAT ID. Alias: `tax_id`."}}}}}},"responses":{"200":{"description":"200 { data: { valid, unavailable, countryCode, vatNumber, requestDate, name, address, errorMessage, customer: Customer } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["customers:write"]}},"/api/v1/customers/duplicates":{"get":{"operationId":"get_customers_duplicates","tags":["customers"],"summary":"Find similar customers by name","description":"**Scope:** `customers:read`\n\n**Response:** { data: [{ id, name, customerNumber, city, isArchived, similarity }] } (at most five, sorted by similarity descending)\n\n- Fuzzy comparison ≥ 0.65 (lib/customer-duplicates.ts), including archived customers. Read-only query.","parameters":[{"name":"name","in":"query","required":true,"description":"At most 200 characters; fewer than three returns {data:[]}.","schema":{"type":"string"}},{"name":"exclude_id","in":"query","required":false,"description":"Alias: `excludeId`.","schema":{"type":"string"}}],"responses":{"200":{"description":"{ data: [{ id, name, customerNumber, city, isArchived, similarity }] } (at most five, sorted by similarity descending)","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"type":"object"}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["customers:read"]}},"/api/v1/customers/by-email/{email}":{"get":{"operationId":"get_customers_by_email_email","tags":["customers"],"summary":"Find a customer by email address","description":"**Scope:** `customers:read`\n\n**Response:** { data: Customer & { invoiceCount } }\n\n- Matches the whole address case-insensitively; _ and % are literal characters, not wildcards. Returns 404 when no customer matches. If several customers share the address, returns the oldest, as in the POST /customers 409 response. Archived customers remain searchable.","parameters":[{"name":"email","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"{ data: Customer & { invoiceCount } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Customer"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["customers:read"]}},"/api/v1/products":{"get":{"operationId":"get_products","tags":["products"],"summary":"List products","description":"**Scope:** `products:read`\n\n**Response:** { data: Product[], pagination }\n\n- No plan gate.\n- include_inactive=false opts into active products only.","parameters":[{"name":"search","in":"query","required":false,"description":"Case-insensitive match on name/sku.","schema":{"type":"string"}},{"name":"isService","in":"query","required":false,"description":"Alias: `is_service`.","schema":{"type":"boolean"}},{"name":"include_inactive","in":"query","required":false,"description":"Deliberate compatibility choice: defaults to true so existing integrations still see all products; the route previously never filtered by isActive.","schema":{"type":"boolean","default":true}},{"name":"limit","in":"query","required":false,"schema":{"type":"number","default":50}},{"name":"offset","in":"query","required":false,"schema":{"type":"number","default":0}}],"responses":{"200":{"description":"{ data: Product[], pagination }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Product"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["products:read"]},"post":{"operationId":"post_products","tags":["products"],"summary":"Create a product","description":"**Scope:** `products:write`\n\n**Response:** 201 { data: { id, name, description, sku, isService, price, taxRate, unit, isActive, createdAt, updatedAt } }\n\n- Audit PRODUCT_CREATED_VIA_API. The response now also includes updatedAt.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"},"sku":{"type":"string","description":"Unique within the company → 409 if already taken."},"isService":{"type":"boolean","default":false,"description":"Alias: `is_service`."},"price":{"type":"number","description":"Must be ≥ 0."},"taxRate":{"type":"number","default":20,"description":"Must be 0–100. Alias: `tax_rate`."},"unit":{"type":"string","description":"Default: \"Stück\"."},"isActive":{"type":"boolean","default":true,"description":"Alias: `is_active`."}},"required":["name","price"]}}}},"responses":{"201":{"description":"201 { data: { id, name, description, sku, isService, price, taxRate, unit, isActive, createdAt, updatedAt } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["products:write"]}},"/api/v1/products/{id}":{"get":{"operationId":"get_products_id","tags":["products"],"summary":"Read product details","description":"**Scope:** `products:read`\n\n**Response:** { data: { id, name, description, sku, isService, price, taxRate, unit, isActive, createdAt, updatedAt } }\n\n- 404 if not found. Inactive products remain readable by ID.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"{ data: { id, name, description, sku, isService, price, taxRate, unit, isActive, createdAt, updatedAt } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["products:read"]},"put":{"operationId":"put_products_id","tags":["products"],"summary":"Update a product","description":"**Scope:** `products:write`\n\n**Response:** 200 { data: { id, name, description, sku, isService, price, taxRate, unit, isActive, createdAt, updatedAt } }\n\n- 404 if not found; 409 on an SKU conflict with another product. Audit PRODUCT_UPDATED_VIA_API. The response now also includes createdAt.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"},"sku":{"type":"string","description":"Unique within the company → 409 if already taken."},"isService":{"type":"boolean","default":false,"description":"Alias: `is_service`."},"price":{"type":"number","description":"Must be ≥ 0."},"taxRate":{"type":"number","default":20,"description":"Must be 0–100. Alias: `tax_rate`."},"unit":{"type":"string","description":"Default: \"Stück\"."},"isActive":{"type":"boolean","default":true,"description":"Alias: `is_active`."}},"description":"All POST fields: All optional/partial, with snake_case aliases through pickField. price/taxRate are validated as for POST."}}}},"responses":{"200":{"description":"200 { data: { id, name, description, sku, isService, price, taxRate, unit, isActive, createdAt, updatedAt } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["products:write"]},"delete":{"operationId":"delete_products_id","tags":["products"],"summary":"Deactivate a product (soft delete)","description":"**Scope:** `products:write`\n\n**Response:** 200 { success: true }\n\n- Sets isActive: false and keeps the row. Audit PRODUCT_DEACTIVATED_VIA_API. 404 if not found.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"200 { success: true }","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["products:write"]}},"/api/v1/quotes":{"get":{"operationId":"get_quotes","tags":["quotes"],"summary":"List quotes","description":"**Scope:** `quotes:read`\n\n**Response:** { data: Quote[], pagination }\n\n- Sorted by issueDate descending. EXPIRED is never stored in the database; it appears only in displayStatus.","parameters":[{"name":"status","in":"query","required":false,"description":"Filter by stored status.","schema":{"type":"string","enum":["DRAFT","SENT","ACCEPTED","REJECTED","EXPIRED","CONVERTED"]}},{"name":"customer_id","in":"query","required":false,"description":"Alias: `customerId`.","schema":{"type":"string"}},{"name":"search","in":"query","required":false,"description":"Case-insensitive match on quote number and customer name.","schema":{"type":"string"}},{"name":"from","in":"query","required":false,"description":"Range over issueDate.","schema":{"type":"string","format":"date"}},{"name":"to","in":"query","required":false,"description":"Range over issueDate.","schema":{"type":"string","format":"date"}},{"name":"category_id","in":"query","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"schema":{"type":"number","default":50}},{"name":"offset","in":"query","required":false,"schema":{"type":"number","default":0}}],"responses":{"200":{"description":"{ data: Quote[], pagination }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Quote"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["quotes:read"]},"post":{"operationId":"post_quotes","tags":["quotes"],"summary":"Create a quote","description":"**Scope:** `quotes:write`\n\n**Response:** 201 { data: Quote }\n\n- Document category check (403/400), plan limit checkDocumentLimit (403), customer must belong to the company (404).\n- Quote number allocated atomically from the AN number range; totals calculated through calculateDocumentTotals with percentage discounts. Audit QUOTE_CREATED_VIA_API. Status is always DRAFT.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"documentLocale":{"type":"string","enum":["de","en"],"description":"On creation only: explicit document language takes precedence over customer/company defaults, then German. Derived documents retain the source language. The response contains documentLocale and documentTextVersion; editing does not change the stored profile. Alias: `document_locale`."},"customerId":{"type":"string"},"validUntil":{"type":"string","format":"date"},"issueDate":{"type":"string","format":"date","description":"Default: now."},"servicePeriodStart":{"type":"string","format":"date","nullable":true},"servicePeriodEnd":{"type":"string","format":"date","nullable":true},"documentCategoryId":{"type":"string","nullable":true},"reverseCharge":{"type":"boolean","default":false},"headerText":{"type":"string","description":"Printed."},"notes":{"type":"string","description":"Internal, not on the PDF."},"footer":{"type":"string","description":"Printed."},"lines":{"type":"array","items":{"type":"object"},"description":"At least one line item; discount is a percentage (0–100)"}},"required":["customerId","validUntil","lines"]}}}},"responses":{"201":{"description":"201 { data: Quote }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Quote"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["quotes:write"]}},"/api/v1/quotes/{id}":{"get":{"operationId":"get_quotes_id","tags":["quotes"],"summary":"Read quote details","description":"**Scope:** `quotes:read`\n\n**Response:** { data: Quote }\n\n- 404 \"Quote not found\" for unknown IDs or another company's record.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"{ data: Quote }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Quote"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["quotes:read"]},"put":{"operationId":"put_quotes_id","tags":["quotes"],"summary":"Update a quote draft","description":"**Scope:** `quotes:write`\n\n**Response:** { data: Quote }\n\n- Drafts only: DRAFT, lockedAt = null, convertedToInvoiceId = null; otherwise 409. The dashboard has no dedicated quote edit route. This route provides editing with the same state rules as deletion.\n- Header and line items are written conditionally in one transaction; a concurrent change returns 409. Audit QUOTE_UPDATED_VIA_API.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"customerId":{"description":"All optional; null clears documentCategoryId/servicePeriod* fields."},"documentCategoryId":{"description":"All optional; null clears documentCategoryId/servicePeriod* fields."},"issueDate":{"description":"All optional; null clears documentCategoryId/servicePeriod* fields."},"validUntil":{"description":"All optional; null clears documentCategoryId/servicePeriod* fields."},"servicePeriodStart":{"description":"All optional; null clears documentCategoryId/servicePeriod* fields."},"servicePeriodEnd":{"description":"All optional; null clears documentCategoryId/servicePeriod* fields."},"reverseCharge":{"description":"All optional; null clears documentCategoryId/servicePeriod* fields."},"headerText":{"description":"All optional; null clears documentCategoryId/servicePeriod* fields."},"notes":{"description":"All optional; null clears documentCategoryId/servicePeriod* fields."},"footer":{"description":"All optional; null clears documentCategoryId/servicePeriod* fields."},"lines":{"type":"array","items":{"type":"object"},"description":"Replaces all line items."}}}}}},"responses":{"200":{"description":"{ data: Quote }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Quote"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["quotes:write"]},"delete":{"operationId":"delete_quotes_id","tags":["quotes"],"summary":"Delete a quote draft","description":"**Scope:** `quotes:write`\n\n**Response:** { data: { id, deleted: true } }\n\n- DRAFT only, unlocked and unconverted (conditional deleteMany); otherwise 409. Audit QUOTE_DELETED_VIA_API.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"{ data: { id, deleted: true } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["quotes:write"]}},"/api/v1/quotes/{id}/status":{"post":{"operationId":"post_quotes_id_status","tags":["quotes"],"summary":"Accept or reject a quote","description":"**Scope:** `quotes:write`\n\n**Response:** 200 { data: Quote }\n\n- When the document leaves draft status, it counts towards the plan document quota: an exhausted quota returns 403.\n- CONVERTED cannot be set directly (400: invoice status results only from conversion); EXPIRED is display-only.\n- Already converted quote → 409; invalid transition, such as ACCEPTED → ACCEPTED, → 409; unknown value → 400 with allowedValues. Audit QUOTE_STATUS_CHANGED_VIA_API.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["ACCEPTED","REJECTED"],"description":"Only ACCEPTED and REJECTED are allowed."}},"required":["status"]}}}},"responses":{"200":{"description":"200 { data: Quote }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Quote"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["quotes:write"]}},"/api/v1/quotes/{id}/send":{"post":{"operationId":"post_quotes_id_send","tags":["quotes"],"summary":"Send a quote by email","description":"**Scope:** `quotes:write`\n\n**Response:** 200 { data: { quoteId, quoteNumber, status, sentTo, cc, bcc } }\n\n- When the document leaves draft status, it counts towards the plan document quota: an exhausted quota returns 403.\n- PDF from lib/quote-pdf.ts, email type QUOTE with an attachment; conditional DRAFT → SENT only after successful delivery.\n- lockDocument(\"QUOTE\") + archiveDistributedPdfAsInitialVersion store the sent attachment as version 1. No recipient → 400; delivery error → 500. Audit QUOTE_SENT_VIA_API.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"recipientEmail":{"type":"string","description":"Alias: `recipient_email`. Default: Customer's email address."},"cc":{"type":"string"},"bcc":{"type":"string"}}}}}},"responses":{"200":{"description":"200 { data: { quoteId, quoteNumber, status, sentTo, cc, bcc } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["quotes:write"]}},"/api/v1/quotes/{id}/pdf":{"get":{"operationId":"get_quotes_id_pdf","tags":["quotes"],"summary":"Download a quote PDF","description":"**Scope:** `quotes:read`\n\n**Response:** application/pdf (binary), Content-Disposition inline, X-PDF-Source: s3-cache | generated.\n\n- Same renderer as the dashboard and email (loadOrGenerateQuotePdf). Fingerprint cache enabled; drafts are never cached. Audit PDF_DOWNLOADED_VIA_API. 404 \"Quote not found\".","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"application/pdf (binary), Content-Disposition inline, X-PDF-Source: s3-cache | generated.","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["quotes:read"]}},"/api/v1/quotes/{id}/convert":{"post":{"operationId":"post_quotes_id_convert","tags":["quotes"],"summary":"Convert a quote to an invoice or order confirmation","description":"**Scope:** `quotes:write`, `invoices:write`\n\n**Response:** 201 { data: { target, quoteId, invoice: {…} } } or { target, quoteId, orderConfirmation } }\n\n- In addition to quotes:write, requires the target scope: invoices:write for target \"invoice\", order-confirmations:write for target \"order-confirmation\". Checked with hasScope and the relevant constant in the route. Missing target scope → 403 with requiredScopes.\n- invoice: as in convertQuoteToInvoice. Claims the quote atomically (CONVERTED + convertedToInvoiceId), creates an invoice draft with a placeholder number, derives its due date from payment terms, converts percentage discounts to absolute invoice discounts, and uses invoice templates rather than quote text.\n- order-confirmation: as in createOrderConfirmationFromQuote. Creates an order confirmation with the quote's line items and percentage discounts; notes is the quote's closing text. The quote remains unchanged and can still be converted.\n- Already converted/rejected → 409. Audit QUOTE_CONVERTED_VIA_API + INVOICE_CREATED_FROM_QUOTE_VIA_API or ORDER_CONFIRMATION_CREATED_VIA_API. Webhook invoice.created only for target invoice.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"target":{"type":"string","enum":["invoice","order-confirmation"],"description":"\"invoice\" or \"order-confirmation\"."}},"required":["target"]}}}},"responses":{"201":{"description":"201 { data: { target, quoteId, invoice: {…} } } or { target, quoteId, orderConfirmation } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["quotes:write","invoices:write"]}},"/api/v1/order-confirmations":{"get":{"operationId":"get_order_confirmations","tags":["order-confirmations"],"summary":"List order confirmations","description":"**Scope:** `order-confirmations:read`\n\n**Response:** { data: OrderConfirmation[], pagination }\n\n- Sorted by issueDate descending. Invalid status → 400 with allowedValues.","parameters":[{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["DRAFT","SENT","CONFIRMED","IN_PROGRESS","COMPLETED","CANCELLED"]}},{"name":"customer_id","in":"query","required":false,"description":"Alias: `customerId`.","schema":{"type":"string"}},{"name":"search","in":"query","required":false,"description":"Order confirmation number and customer name.","schema":{"type":"string"}},{"name":"from","in":"query","required":false,"description":"By issueDate.","schema":{"type":"string","format":"date"}},{"name":"to","in":"query","required":false,"description":"By issueDate.","schema":{"type":"string","format":"date"}},{"name":"category_id","in":"query","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"schema":{"type":"number","default":50}},{"name":"offset","in":"query","required":false,"schema":{"type":"number","default":0}}],"responses":{"200":{"description":"{ data: OrderConfirmation[], pagination }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/OrderConfirmation"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["order-confirmations:read"]},"post":{"operationId":"post_order_confirmations","tags":["order-confirmations"],"summary":"Create an order confirmation","description":"**Scope:** `order-confirmations:write`\n\n**Response:** 201 { data: OrderConfirmation }\n\n- Checks the document category and company ownership of the customer (404); allocates an AB number; calculates totals through calculateDocumentTotals with percentage discounts.\n- Checks the document quota with checkDocumentLimit (403), matching the dashboard. Audit ORDER_CONFIRMATION_CREATED_VIA_API. Status is always DRAFT.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"documentLocale":{"type":"string","enum":["de","en"],"description":"On creation only: explicit document language takes precedence over customer/company defaults, then German. Derived documents retain the source language. The response contains documentLocale and documentTextVersion; editing does not change the stored profile. Alias: `document_locale`."},"customerId":{"type":"string"},"issueDate":{"type":"string","format":"date","description":"Default: now."},"deliveryDate":{"type":"string","format":"date","nullable":true},"validUntil":{"type":"string","format":"date","nullable":true},"documentCategoryId":{"type":"string","nullable":true},"reverseCharge":{"type":"boolean","default":false},"notes":{"type":"string","description":"Printed."},"footer":{"type":"string"},"deliveryTerms":{"type":"string"},"paymentTerms":{"type":"string"},"quoteId":{"type":"string","description":"Optional quote reference. Alias: `quote_id`."},"lines":{"type":"array","items":{"type":"object"},"description":"At least one line item; discount as a percentage."}},"required":["customerId","lines"]}}}},"responses":{"201":{"description":"201 { data: OrderConfirmation }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/OrderConfirmation"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["order-confirmations:write"]}},"/api/v1/order-confirmations/{id}":{"get":{"operationId":"get_order_confirmations_id","tags":["order-confirmations"],"summary":"Read order confirmation details","description":"**Scope:** `order-confirmations:read`\n\n**Response:** { data: OrderConfirmation }\n\n- 404 \"Order confirmation not found\".","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"{ data: OrderConfirmation }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/OrderConfirmation"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["order-confirmations:read"]},"put":{"operationId":"put_order_confirmations_id","tags":["order-confirmations"],"summary":"Update an order confirmation draft","description":"**Scope:** `order-confirmations:write`\n\n**Response:** { data: OrderConfirmation }\n\n- Drafts only (DRAFT), otherwise 409. Audit ORDER_CONFIRMATION_UPDATED_VIA_API.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"customerId":{"description":"All optional; null clears documentCategoryId/deliveryDate/validUntil."},"documentCategoryId":{"description":"All optional; null clears documentCategoryId/deliveryDate/validUntil."},"issueDate":{"description":"All optional; null clears documentCategoryId/deliveryDate/validUntil."},"deliveryDate":{"description":"All optional; null clears documentCategoryId/deliveryDate/validUntil."},"validUntil":{"description":"All optional; null clears documentCategoryId/deliveryDate/validUntil."},"reverseCharge":{"description":"All optional; null clears documentCategoryId/deliveryDate/validUntil."},"notes":{"description":"All optional; null clears documentCategoryId/deliveryDate/validUntil."},"footer":{"description":"All optional; null clears documentCategoryId/deliveryDate/validUntil."},"deliveryTerms":{"description":"All optional; null clears documentCategoryId/deliveryDate/validUntil."},"paymentTerms":{"description":"All optional; null clears documentCategoryId/deliveryDate/validUntil."},"lines":{"type":"array","items":{"type":"object"},"description":"Replaces all line items."}}}}}},"responses":{"200":{"description":"{ data: OrderConfirmation }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/OrderConfirmation"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["order-confirmations:write"]},"delete":{"operationId":"delete_order_confirmations_id","tags":["order-confirmations"],"summary":"Delete an order confirmation draft","description":"**Scope:** `order-confirmations:write`\n\n**Response:** { data: { id, deleted: true } }\n\n- DRAFT only, otherwise 409. Audit ORDER_CONFIRMATION_DELETED_VIA_API.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"{ data: { id, deleted: true } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["order-confirmations:write"]}},"/api/v1/order-confirmations/{id}/status":{"post":{"operationId":"post_order_confirmations_id_status","tags":["order-confirmations"],"summary":"Set an order confirmation's status","description":"**Scope:** `order-confirmations:write`\n\n**Response:** 200 { data: OrderConfirmation }\n\n- When the document leaves draft status, it counts towards the plan document quota: an exhausted quota returns 403 (cancelling a draft remains possible).\n- Conditional status update only while the read status and absent invoice reference remain unchanged; conflict or already converted → 409. COMPLETED results only from conversion. Audit ORDER_CONFIRMATION_STATUS_CHANGED_VIA_API.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["DRAFT","SENT","CONFIRMED","IN_PROGRESS","COMPLETED","CANCELLED"],"description":"Only transitions allowed by document status rules; COMPLETED cannot be set directly."}},"required":["status"]}}}},"responses":{"200":{"description":"200 { data: OrderConfirmation }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/OrderConfirmation"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["order-confirmations:write"]}},"/api/v1/order-confirmations/{id}/send":{"post":{"operationId":"post_order_confirmations_id_send","tags":["order-confirmations"],"summary":"Send an order confirmation by email","description":"**Scope:** `order-confirmations:write`\n\n**Response:** 200 { data: { orderConfirmationId, confirmationNumber, status, sentTo, cc, bcc } }\n\n- When the document leaves draft status, it counts towards the plan document quota: an exhausted quota returns 403.\n- PDF from lib/order-confirmation-pdf.ts, email type ORDER_CONFIRMATION; conditional DRAFT → SENT only after successful delivery.\n- lockDocument(\"ORDER_CONFIRMATION\") and archive the sent attachment as version 1. No recipient → 400; delivery error → 500. Audit ORDER_CONFIRMATION_SENT_VIA_API.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"recipientEmail":{"type":"string","description":"Alias: `recipient_email`. Default: Customer's email address."},"cc":{"type":"string"},"bcc":{"type":"string"}}}}}},"responses":{"200":{"description":"200 { data: { orderConfirmationId, confirmationNumber, status, sentTo, cc, bcc } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["order-confirmations:write"]}},"/api/v1/order-confirmations/{id}/pdf":{"get":{"operationId":"get_order_confirmations_id_pdf","tags":["order-confirmations"],"summary":"Download an order confirmation PDF","description":"**Scope:** `order-confirmations:read`\n\n**Response:** application/pdf (binary), Content-Disposition inline, X-PDF-Source.\n\n- Same renderer as dashboard/email. Fingerprint cache; drafts are not cached. Audit PDF_DOWNLOADED_VIA_API.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"application/pdf (binary), Content-Disposition inline, X-PDF-Source.","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["order-confirmations:read"]}},"/api/v1/order-confirmations/{id}/convert":{"post":{"operationId":"post_order_confirmations_id_convert","tags":["order-confirmations"],"summary":"Convert an order confirmation to an invoice or order","description":"**Scope:** `order-confirmations:write`, `invoices:write`\n\n**Response:** 201 { data: { target, orderConfirmationId, invoice: {…} } } or { target, orderConfirmationId, order: {…} } }\n\n- In addition to order-confirmations:write, requires invoices:write for target \"invoice\" or orders:write for target \"order\", checked through hasScope in the route. Missing target scope → 403.\n- invoice: as in convertOrderConfirmationToInvoice. Checks category, at least one line item and checkDocumentLimit (403). Claims the confirmation atomically (status → COMPLETED), creates an invoice draft with a placeholder number, converts percentage discounts to absolute discounts, and sets notes to a source order confirmation reference.\n- order: uses createOrderFromOrderConfirmation (lib/orders.ts). Creates an AU-numbered order with the confirmation's items/percentage discounts and references to the confirmation and source quote. The confirmation stays unchanged.\n- Already converted → 409. Audit ORDER_CONFIRMATION_CONVERTED_VIA_API + INVOICE_CREATED_FROM_ORDER_CONFIRMATION_VIA_API. Webhook invoice.created after commit, only for target invoice.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"target":{"type":"string","enum":["invoice","order"],"description":"\"invoice\" or \"order\"."}},"required":["target"]}}}},"responses":{"201":{"description":"201 { data: { target, orderConfirmationId, invoice: {…} } } or { target, orderConfirmationId, order: {…} } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["order-confirmations:write","invoices:write"]}},"/api/v1/delivery-notes":{"get":{"operationId":"get_delivery_notes","tags":["delivery-notes"],"summary":"List delivery notes","description":"**Scope:** `delivery-notes:read`\n\n**Response:** 200 { data: DeliveryNote[], pagination }\n\n- Sorted by issueDate descending. Invalid status → 400 with allowedValues.","parameters":[{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["DRAFT","SENT","DELIVERED","CONVERTED"]}},{"name":"customer_id","in":"query","required":false,"description":"Alias: `customerId`.","schema":{"type":"string"}},{"name":"search","in":"query","required":false,"description":"Delivery note number or customer name.","schema":{"type":"string"}},{"name":"from","in":"query","required":false,"description":"issueDate ≥/≤.","schema":{"type":"string","format":"date"}},{"name":"to","in":"query","required":false,"description":"issueDate ≥/≤.","schema":{"type":"string","format":"date"}},{"name":"category_id","in":"query","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"schema":{"type":"number","default":50}},{"name":"offset","in":"query","required":false,"schema":{"type":"number","default":0}}],"responses":{"200":{"description":"200 { data: DeliveryNote[], pagination }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/DeliveryNote"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["delivery-notes:read"]},"post":{"operationId":"post_delivery_notes","tags":["delivery-notes"],"summary":"Create a delivery note","description":"**Scope:** `delivery-notes:write`\n\n**Response:** 201 { data: DeliveryNote }\n\n- Assigns LS-{YEAR}-{0001}. Checks the plan's document quota (→ 403 plan_no_access).\n- Customer/order must belong to the company (→ 404); unknown productId → 400. Audit DELIVERY_NOTE_CREATED_VIA_API. Starts as DRAFT.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"documentLocale":{"type":"string","enum":["de","en"],"description":"On creation only: explicit document language takes precedence over customer/company defaults, then German. Derived documents retain the source language. The response contains documentLocale and documentTextVersion; editing does not change the stored profile. Alias: `document_locale`."},"customerId":{"type":"string"},"lines":{"type":"array","items":{"type":"object","properties":{"description":{"type":"string","description":"Required unless productId is set."},"quantity":{"type":"number","default":1},"unit":{"type":"string","description":"Default: Product unit or \"Stk\"."},"productId":{"type":"string","description":"Fills description/unit like the dashboard product selector."}}},"description":"At least one item; description/quantity/unit only, no prices."},"deliveryDate":{"type":"string","format":"date"},"shippingAddress":{"type":"string"},"notes":{"type":"string"},"documentCategoryId":{"type":"string","nullable":true},"orderId":{"type":"string","nullable":true,"description":"Links to an order."}},"required":["customerId","lines"]}}}},"responses":{"201":{"description":"201 { data: DeliveryNote }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/DeliveryNote"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["delivery-notes:write"]}},"/api/v1/delivery-notes/{id}":{"get":{"operationId":"get_delivery_notes_id","tags":["delivery-notes"],"summary":"Read delivery note details","description":"**Scope:** `delivery-notes:read`\n\n**Response:** 200 { data: DeliveryNote }\n\n- 404 if the delivery note does not belong to the company.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"200 { data: DeliveryNote }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/DeliveryNote"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["delivery-notes:read"]},"put":{"operationId":"put_delivery_notes_id","tags":["delivery-notes"],"summary":"Update a delivery note draft","description":"**Scope:** `delivery-notes:write`\n\n**Response:** 200 { data: DeliveryNote }\n\n- Drafts only, otherwise 409. The dashboard has no delivery note edit page; the rule follows the deletion rule. Audit DELIVERY_NOTE_UPDATED_VIA_API.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"customerId":{"description":"All optional; null clears documentCategoryId/deliveryDate/orderId."},"documentCategoryId":{"description":"All optional; null clears documentCategoryId/deliveryDate/orderId."},"deliveryDate":{"description":"All optional; null clears documentCategoryId/deliveryDate/orderId."},"shippingAddress":{"description":"All optional; null clears documentCategoryId/deliveryDate/orderId."},"notes":{"description":"All optional; null clears documentCategoryId/deliveryDate/orderId."},"orderId":{"description":"All optional; null clears documentCategoryId/deliveryDate/orderId."},"lines":{"type":"array","items":{"type":"object"},"description":"Replaces all line items and renumbers them starting at 1."}}}}}},"responses":{"200":{"description":"200 { data: DeliveryNote }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/DeliveryNote"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["delivery-notes:write"]},"delete":{"operationId":"delete_delivery_notes_id","tags":["delivery-notes"],"summary":"Delete a delivery note draft","description":"**Scope:** `delivery-notes:write`\n\n**Response:** 200 { data: { id, deleted: true } }\n\n- Drafts only, otherwise 409. Audit DELIVERY_NOTE_DELETED_VIA_API.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"200 { data: { id, deleted: true } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["delivery-notes:write"]}},"/api/v1/delivery-notes/{id}/status":{"post":{"operationId":"post_delivery_notes_id_status","tags":["delivery-notes"],"summary":"Set a delivery note's status","description":"**Scope:** `delivery-notes:write`\n\n**Response:** 200 { data: DeliveryNote }\n\n- When the document leaves draft status, it counts towards the plan document quota: an exhausted quota returns 403.\n- Audit DELIVERY_NOTE_STATUS_CHANGED_VIA_API with { status: { old, new } }.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["DRAFT","SENT","DELIVERED"],"description":"CONVERTED cannot be set manually → 400 with a reference to /convert."}},"required":["status"]}}}},"responses":{"200":{"description":"200 { data: DeliveryNote }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/DeliveryNote"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["delivery-notes:write"]}},"/api/v1/delivery-notes/{id}/send":{"post":{"operationId":"post_delivery_notes_id_send","tags":["delivery-notes"],"summary":"Send a delivery note by email","description":"**Scope:** `delivery-notes:write`\n\n**Response:** 200 { data: { deliveryNoteId, status, sentTo, cc, bcc, sentAt } }\n\n- When the document leaves draft status, it counts towards the plan document quota: an exhausted quota returns 403.\n- PDF attachment (EmailType.DELIVERY), creates a SentEmail row. Then DRAFT → SENT only while unconverted; lockDocument + archiveDistributedPdfAsInitialVersion create v1.\n- Neither customer email nor recipientEmail → 400. Failed delivery → 500, without a status change.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"recipientEmail":{"type":"string","description":"Default: Customer's email address."},"cc":{"type":"string"},"bcc":{"type":"string"}}}}}},"responses":{"200":{"description":"200 { data: { deliveryNoteId, status, sentTo, cc, bcc, sentAt } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["delivery-notes:write"]}},"/api/v1/delivery-notes/{id}/pdf":{"get":{"operationId":"get_delivery_notes_id_pdf","tags":["delivery-notes"],"summary":"Download a delivery note PDF","description":"**Scope:** `delivery-notes:read`\n\n**Response:** 200 binary data, Content-Type application/pdf, X-PDF-Source: s3-cache|generated.\n\n- Same renderer as the dashboard (lib/delivery-note-pdf.ts), including fingerprint caching; drafts are never cached. Audit PDF_DOWNLOADED_VIA_API only when found.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"200 binary data, Content-Type application/pdf, X-PDF-Source: s3-cache|generated.","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["delivery-notes:read"]}},"/api/v1/delivery-notes/{id}/convert":{"post":{"operationId":"post_delivery_notes_id_convert","tags":["delivery-notes"],"summary":"Convert a delivery note to an invoice, adding prices","description":"**Scope:** `delivery-notes:write`, `invoices:write`\n\n**Response:** 201 { data: { deliveryNoteId, invoiceId, invoice: { id, invoiceNumber, status, issueDate, dueDate, currency, netTotal, taxTotal, grossTotal, customer, lines[] } } }\n\n- Claims the delivery note atomically with a 20-second budget: status → CONVERTED and convertedToInvoiceId set. Concurrent conversion receives 409.\n- Creates an invoice draft with an ENTWURF-… placeholder number, text from invoice templates and a source delivery note reference in notes. Items without matching linesPricing receive unitPrice 0 / taxRate 20.\n- Two audit rows: DELIVERY_NOTE_CONVERTED_VIA_API for DeliveryNote and INVOICE_CREATED_FROM_DELIVERY_NOTE_VIA_API for Invoice.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"linesPricing":{"type":"array","items":{"type":"object","properties":{"position":{},"unitPrice":{},"taxRate":{}}},"description":"At least one item; position refers to DeliveryNoteLine.position."},"dueDate":{"type":"string","format":"date","description":"Default: Customer's payment term, otherwise company's, otherwise 14 days."}},"required":["linesPricing"]}}}},"responses":{"201":{"description":"201 { data: { deliveryNoteId, invoiceId, invoice: { id, invoiceNumber, status, issueDate, dueDate, currency, netTotal, taxTotal, grossTotal, customer, lines[] } } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["delivery-notes:write","invoices:write"]}},"/api/v1/orders":{"get":{"operationId":"get_orders","tags":["orders"],"summary":"List orders","description":"**Scope:** `orders:read`\n\n**Response:** 200 { data: Order[], pagination }\n\n- Sorted by createdAt descending.","parameters":[{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["DRAFT","ACTIVE","ON_HOLD","COMPLETED","CANCELLED"]}},{"name":"customer_id","in":"query","required":false,"description":"Alias: `customerId`.","schema":{"type":"string"}},{"name":"search","in":"query","required":false,"description":"Order number, title or customer name.","schema":{"type":"string"}},{"name":"category_id","in":"query","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"schema":{"type":"number","default":50}},{"name":"offset","in":"query","required":false,"schema":{"type":"number","default":0}}],"responses":{"200":{"description":"200 { data: Order[], pagination }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Order"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["orders:read"]},"post":{"operationId":"post_orders","tags":["orders"],"summary":"Create an order","description":"**Scope:** `orders:write`\n\n**Response:** 201 { data: Order }\n\n- Assigns AU-{YEAR}-{0001}. totalBudget is calculated from line items as a net total through calculateDocumentTotals (percent mode); deduction lines reduce the budget.\n- Checks the document quota with checkDocumentLimit, matching the dashboard. Audit ORDER_CREATED_VIA_API. Starts as DRAFT with deliveredQty/invoicedQty at 0.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"documentLocale":{"type":"string","enum":["de","en"],"description":"On creation only: explicit document language takes precedence over customer/company defaults, then German. Derived documents retain the source language. The response contains documentLocale and documentTextVersion; editing does not change the stored profile. Alias: `document_locale`."},"customerId":{"type":"string"},"name":{"type":"string","description":"Order title."},"lines":{"type":"array","items":{"type":"object","properties":{"description":{"type":"string","description":"Required unless productId is provided."},"quantity":{"type":"number","default":1},"unit":{"type":"string","description":"Default: Product unit or \"Stk\"."},"unitPrice":{"type":"number","description":"Default: Product price or 0."},"taxRate":{"type":"number","description":"Default: Product tax rate or 20."},"discount":{"type":"number","default":0,"description":"Percentage, 0–100."},"lineType":{"type":"string","enum":["PRODUCT","SERVICE","DISCOUNT_PERCENT","DISCOUNT_ABSOLUTE","SHIPPING","ADVANCE_PAYMENT","CREDIT","TEXT","SUBTOTAL","PAGEBREAK"],"default":"PRODUCT"},"productId":{"type":"string"}}},"description":"At least one."},"description":{"type":"string"},"notes":{"type":"string"},"documentCategoryId":{"type":"string","nullable":true},"startDate":{"type":"string","format":"date","nullable":true},"endDate":{"type":"string","format":"date","nullable":true},"quoteId":{"type":"string","description":"Must belong to the company (→ 404)"},"orderConfirmationId":{"type":"string","description":"Must belong to the company (→ 404)"}},"required":["customerId","name","lines"]}}}},"responses":{"201":{"description":"201 { data: Order }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Order"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["orders:write"]}},"/api/v1/orders/{id}":{"get":{"operationId":"get_orders_id","tags":["orders"],"summary":"Read order details and statistics","description":"**Scope:** `orders:read`\n\n**Response:** 200 { data: Order & { stats: OrderStats, invoices: [...], deliveryNotes: [...] } }\n\n- stats matches getOrderStats on the detail page. Deduction, text and subtotal lines are excluded from quantity progress.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"200 { data: Order & { stats: OrderStats, invoices: [...], deliveryNotes: [...] } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Order"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["orders:read"]},"put":{"operationId":"put_orders_id","tags":["orders"],"summary":"Update an order","description":"**Scope:** `orders:write`\n\n**Response:** 200 { data: Order }\n\n- DRAFT and ACTIVE only, otherwise 409. The customer cannot be changed. Audit ORDER_UPDATED_VIA_API.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"},"notes":{"type":"string"},"documentCategoryId":{"type":"string","nullable":true},"startDate":{"type":"string","format":"date","nullable":true},"endDate":{"type":"string","format":"date","nullable":true},"lines":{"type":"array","items":{"type":"object"},"description":"Replaces all line items (deliveredQty/invoicedQty restart at 0); totalBudget is recalculated."}}}}}},"responses":{"200":{"description":"200 { data: Order }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Order"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["orders:write"]},"delete":{"operationId":"delete_orders_id","tags":["orders"],"summary":"Delete an order draft","description":"**Scope:** `orders:write`\n\n**Response:** 200 { data: { id, deleted: true } }\n\n- Drafts only, otherwise 409. Audit ORDER_DELETED_VIA_API.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"200 { data: { id, deleted: true } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["orders:write"]}},"/api/v1/orders/{id}/status":{"post":{"operationId":"post_orders_id_status","tags":["orders"],"summary":"Set an order's status","description":"**Scope:** `orders:write`\n\n**Response:** 200 { data: Order }\n\n- When the document leaves draft status, it counts towards the plan document quota: an exhausted quota returns 403 (cancelling a draft remains possible).\n- The first transition out of DRAFT locks the order (lockDocument: lockedAt + v1 snapshot). Idempotent: reactivation from ON_HOLD/COMPLETED does not create another v1.\n- Audit ORDER_STATUS_CHANGED_VIA_API.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["DRAFT","ACTIVE","ON_HOLD","COMPLETED","CANCELLED"]}},"required":["status"]}}}},"responses":{"200":{"description":"200 { data: Order }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Order"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["orders:write"]}},"/api/v1/orders/{id}/lines/{lineId}":{"put":{"operationId":"put_orders_id_lines_lineId","tags":["orders"],"summary":"Update an order line's delivered or invoiced quantity","description":"**Scope:** `orders:write`\n\n**Response:** 200 { data: { id, orderId, position, description, quantity, unit, unitPrice, taxRate, discount, discountMode: \"percent\", lineType, lineTotal, deliveredQty, invoicedQty } }\n\n- Matches updateOrderLineDeliveredQty/updateOrderLineInvoicedQty on the detail page. The line must belong to this order and company, otherwise 404. No quantity supplied → 400.\n- Audit ORDER_LINE_QUANTITY_UPDATED_VIA_API on the order, with lineId.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}},{"name":"lineId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"deliveredQuantity":{"type":"number","description":"≥ 0."},"invoicedQuantity":{"type":"number","description":"≥ 0; at least one of the two is required."}}}}}},"responses":{"200":{"description":"200 { data: { id, orderId, position, description, quantity, unit, unitPrice, taxRate, discount, discountMode: \"percent\", lineType, lineTotal, deliveredQty, invoicedQty } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["orders:write"]}},"/api/v1/orders/{id}/pdf":{"get":{"operationId":"get_orders_id_pdf","tags":["orders"],"summary":"Download an order PDF","description":"**Scope:** `orders:read`\n\n**Response:** 200 binary data, Content-Type application/pdf, X-PDF-Source.\n\n- Same renderer as the dashboard (lib/order-pdf.ts), including fingerprint caching. Shows ordered/delivered/invoiced quantities and progress. Audit PDF_DOWNLOADED_VIA_API only when found.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"200 binary data, Content-Type application/pdf, X-PDF-Source.","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["orders:read"]}},"/api/v1/recurring-schedules":{"get":{"operationId":"get_recurring_schedules","tags":["recurring-schedules"],"summary":"List schedules","description":"**Scope:** `recurring:read`\n\n**Response:** { data: Schedule[], meta: { total, limit, offset, hasMore } }\n\n- Archived schedules are hidden unless status or include_archived=true is supplied. Sorted by nextGenerationDate ascending, then createdAt descending.","parameters":[{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["ACTIVE","PAUSED","ENDED","ARCHIVED"]}},{"name":"customer_id","in":"query","required":false,"schema":{"type":"string"}},{"name":"include_archived","in":"query","required":false,"schema":{"type":"boolean","default":false}},{"name":"limit","in":"query","required":false,"schema":{"type":"number","default":50}},{"name":"offset","in":"query","required":false,"schema":{"type":"number","default":0}}],"responses":{"200":{"description":"{ data: Schedule[], meta: { total, limit, offset, hasMore } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Schedule"}},"meta":{"$ref":"#/components/schemas/Pagination"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["recurring:read"]},"post":{"operationId":"post_recurring_schedules","tags":["recurring-schedules"],"summary":"Create a recurring invoice schedule","description":"**Scope:** `recurring:write`\n**Plan feature:** `recurringInvoices`\n\n**Response:** 201 { data: Schedule }\n\n- Plan gate recurringInvoices (403). Checks customer (404) and document category (400). nextGenerationDate is the start date if in the future, otherwise calculated from the frequency.\n- Audit RECURRING_SCHEDULE_CREATED_VIA_API.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"documentLocale":{"type":"string","enum":["de","en"],"description":"On creation only: explicit document language takes precedence over customer/company defaults, then German. Derived documents retain the source language. The response contains documentLocale and documentTextVersion; editing does not change the stored profile. Alias: `document_locale`."},"customerId":{"type":"string"},"frequency":{"type":"string","enum":["WEEKLY","BIWEEKLY","MONTHLY","QUARTERLY","SEMIANNUALLY","ANNUALLY"]},"startDate":{"type":"string","format":"date"},"lines":{"type":"array","items":{"type":"object"},"description":"At least one line item; each accepts description (required), quantity, unitPrice, taxRate (required), discount (absolute), lineType and articleNumber."},"name":{"type":"string","description":"Default: \"{customer} - {frequency} - {start date}\"."},"description":{"type":"string"},"intervalMultiplier":{"type":"number","default":1,"description":"1–99."},"dayOfMonth":{"type":"number","description":"1–31."},"dayOfWeek":{"type":"number","description":"0–6; stored without shifting the date."},"useLastDayOfMonth":{"type":"boolean","default":false,"description":"Sets dayOfMonth to null."},"endDate":{"type":"string","format":"date"},"maxOccurrences":{"type":"number","description":"≥ 1."},"createAsDraft":{"type":"boolean","default":false},"autoSendEmail":{"type":"boolean","default":true},"notifyOnCreate":{"type":"boolean","default":false},"markAsPaid":{"type":"boolean","default":false},"emailCc":{"type":"string","description":"Comma-separated addresses, normalised."},"emailBcc":{"type":"string","description":"Comma-separated addresses, normalised."},"taxExemptionReason":{"type":"string","enum":["NONE","SMALL_BUSINESS","REVERSE_CHARGE_EU","REVERSE_CHARGE_EU_NO_VAT","THIRD_COUNTRY","INTRA_COMMUNITY","EXPORT_DELIVERY","REVERSE_CHARGE_13B","INTERNAL_SUPPLY","OTHER_EXEMPTION"],"default":"NONE"},"taxExemptionNote":{"type":"string"},"taxCountry":{"type":"string","format":"date","description":"EU codes only (AT, DE, …). Other values are not adopted: the country is inferred from tax rates, or left empty. The response contains the stored value. Default: inferred from tax rates."},"headerText":{"type":"string"},"notes":{"type":"string"},"footer":{"type":"string"},"paymentTermDays":{"type":"number","description":"0–365."},"discountPercent":{"type":"number","description":"0–100."},"discountDays":{"type":"number","description":"0–365."},"orderNumber":{"type":"string"},"buyerReference":{"type":"string"},"externalId":{"type":"string"},"totalDiscountPercent":{"type":"number","description":"0–100."},"isOSS":{"type":"boolean"},"showServiceDateNotice":{"type":"boolean"},"documentCategoryId":{"type":"string","nullable":true}},"required":["customerId","frequency","startDate","lines"]}}}},"responses":{"201":{"description":"201 { data: Schedule }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Schedule"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["recurring:write"],"x-plan-feature":"recurringInvoices"}},"/api/v1/recurring-schedules/{id}":{"get":{"operationId":"get_recurring_schedules_id","tags":["recurring-schedules"],"summary":"Read schedule details and generated invoices","description":"**Scope:** `recurring:read`\n\n**Response:** { data: Schedule & { generatedInvoices: [{ id, invoiceNumber, status, issueDate, grossTotal }] } }\n\n- nextGenerationDate and generatedCount are part of Schedule.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"{ data: Schedule & { generatedInvoices: [{ id, invoiceNumber, status, issueDate, grossTotal }] } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Schedule"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["recurring:read"]},"put":{"operationId":"put_recurring_schedules_id","tags":["recurring-schedules"],"summary":"Update a schedule","description":"**Scope:** `recurring:write`\n\n**Response:** { data: Schedule }\n\n- Changing the cadence (frequency, intervalMultiplier, dayOfMonth, dayOfWeek, useLastDayOfMonth, startDate) recalculates nextGenerationDate. A new customer is checked (404) and the category validated (400).\n- As on creation, taxCountry accepts only EU codes. Other values fall back to the country inferred from tax rates, using the schedule's stored items if lines are omitted. The response contains the resulting stored value.\n- Audit RECURRING_SCHEDULE_UPDATED_VIA_API.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"documentLocale":{"type":"string","enum":["de","en"],"description":"On creation only: explicit document language takes precedence over customer/company defaults, then German. Derived documents retain the source language. The response contains documentLocale and documentTextVersion; editing does not change the stored profile. Alias: `document_locale`."},"customerId":{"type":"string"},"frequency":{"type":"string","enum":["WEEKLY","BIWEEKLY","MONTHLY","QUARTERLY","SEMIANNUALLY","ANNUALLY"]},"startDate":{"type":"string","format":"date"},"lines":{"type":"array","items":{"type":"object"},"description":"At least one line item; each accepts description (required), quantity, unitPrice, taxRate (required), discount (absolute), lineType and articleNumber."},"name":{"type":"string","description":"Default: \"{customer} - {frequency} - {start date}\"."},"description":{"type":"string"},"intervalMultiplier":{"type":"number","default":1,"description":"1–99."},"dayOfMonth":{"type":"number","description":"1–31."},"dayOfWeek":{"type":"number","description":"0–6; stored without shifting the date."},"useLastDayOfMonth":{"type":"boolean","default":false,"description":"Sets dayOfMonth to null."},"endDate":{"type":"string","format":"date"},"maxOccurrences":{"type":"number","description":"≥ 1."},"createAsDraft":{"type":"boolean","default":false},"autoSendEmail":{"type":"boolean","default":true},"notifyOnCreate":{"type":"boolean","default":false},"markAsPaid":{"type":"boolean","default":false},"emailCc":{"type":"string","description":"Comma-separated addresses, normalised."},"emailBcc":{"type":"string","description":"Comma-separated addresses, normalised."},"taxExemptionReason":{"type":"string","enum":["NONE","SMALL_BUSINESS","REVERSE_CHARGE_EU","REVERSE_CHARGE_EU_NO_VAT","THIRD_COUNTRY","INTRA_COMMUNITY","EXPORT_DELIVERY","REVERSE_CHARGE_13B","INTERNAL_SUPPLY","OTHER_EXEMPTION"],"default":"NONE"},"taxExemptionNote":{"type":"string"},"taxCountry":{"type":"string","format":"date","description":"EU codes only (AT, DE, …). Other values are not adopted: the country is inferred from tax rates, or left empty. The response contains the stored value. Default: inferred from tax rates."},"headerText":{"type":"string"},"notes":{"type":"string"},"footer":{"type":"string"},"paymentTermDays":{"type":"number","description":"0–365."},"discountPercent":{"type":"number","description":"0–100."},"discountDays":{"type":"number","description":"0–365."},"orderNumber":{"type":"string"},"buyerReference":{"type":"string"},"externalId":{"type":"string"},"totalDiscountPercent":{"type":"number","description":"0–100."},"isOSS":{"type":"boolean"},"showServiceDateNotice":{"type":"boolean"},"documentCategoryId":{"type":"string","nullable":true}},"description":"All POST fields: All optional; only supplied fields change. null clears endDate, maxOccurrences, dayOfMonth, dayOfWeek, paymentTermDays, discountDays, discountPercent and totalDiscountPercent. status is not accepted here."}}}},"responses":{"200":{"description":"{ data: Schedule }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Schedule"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["recurring:write"]},"delete":{"operationId":"delete_recurring_schedules_id","tags":["recurring-schedules"],"summary":"Delete a schedule","description":"**Scope:** `recurring:write`\n\n**Response:** { data: { id, deleted: true } }\n\n- Only if no invoices have been generated; otherwise 409 with archiveEndpoint. Audit RECURRING_SCHEDULE_DELETED_VIA_API.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"{ data: { id, deleted: true } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["recurring:write"]}},"/api/v1/recurring-schedules/{id}/status":{"post":{"operationId":"post_recurring_schedules_id_status","tags":["recurring-schedules"],"summary":"Set a schedule's status","description":"**Scope:** `recurring:write`\n\n**Response:** 200 { data: Schedule }\n\n- ENDED → ACTIVE is blocked (409). On activation, a future start date takes precedence; resuming from PAUSED recalculates from today. ENDED clears nextGenerationDate. ARCHIVED uses /archive.\n- Audit RECURRING_SCHEDULE_STATUS_CHANGED_VIA_API.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["ACTIVE","PAUSED","ENDED"]}},"required":["status"]}}}},"responses":{"200":{"description":"200 { data: Schedule }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Schedule"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["recurring:write"]}},"/api/v1/recurring-schedules/{id}/archive":{"post":{"operationId":"post_recurring_schedules_id_archive","tags":["recurring-schedules"],"summary":"Archive a schedule","description":"**Scope:** `recurring:write`\n\n**Response:** 200 { data: Schedule }\n\n- Sets ARCHIVED and clears nextGenerationDate. Already archived → 409. Audit RECURRING_SCHEDULE_ARCHIVED_VIA_API.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"200 { data: Schedule }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Schedule"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["recurring:write"]}},"/api/v1/recurring-schedules/{id}/restore":{"post":{"operationId":"post_recurring_schedules_id_restore","tags":["recurring-schedules"],"summary":"Restore an archived schedule","description":"**Scope:** `recurring:write`\n\n**Response:** 200 { data: Schedule }\n\n- Only from ARCHIVED, otherwise 409; always restores to PAUSED. nextGenerationDate stays empty until activation. Audit RECURRING_SCHEDULE_RESTORED_VIA_API.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"200 { data: Schedule }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Schedule"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["recurring:write"]}},"/api/v1/recurring-schedules/{id}/generate":{"post":{"operationId":"post_recurring_schedules_id_generate","tags":["recurring-schedules"],"summary":"Generate an invoice from the schedule now","description":"**Scope:** `recurring:write`\n**Plan feature:** `recurringInvoices`\n\n**Response:** 201 { data: { id, invoiceNumber, status, issueDate, dueDate, currency, netTotal, taxTotal, grossTotal, customerId }, schedule: { id, status, generatedCount, lastGeneratedAt, nextGenerationDate }, warnings?: string[] }\n\n- If the invoice is created with a status other than draft, it immediately counts towards the document quota: an exhausted quota returns 403 plan_limit_reached without consuming a number.\n- Plan gate recurringInvoices (403). Uses the dashboard's shared logic: number allocation (ENTWURF-… for drafts); status from createAsDraft/autoSendEmail/markAsPaid; PDF email delivery for autoSendEmail including emailCc/emailBcc, with delivery failures in warnings; in-app notification RECURRING_GENERATED for notifyOnCreate.\n- Reaching maxOccurrences sets the schedule to ENDED. 409 if not ACTIVE (NOT_ACTIVE), the start date is in the future without force (BEFORE_START_DATE), the maximum is reached (MAX_OCCURRENCES), or no valid line items exist.\n- Audit RECURRING_SCHEDULE_GENERATED_VIA_API.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"force":{"type":"boolean","default":false,"description":"Generate even if the start date is in the future."}}}}}},"responses":{"201":{"description":"201 { data: { id, invoiceNumber, status, issueDate, dueDate, currency, netTotal, taxTotal, grossTotal, customerId }, schedule: { id, status, generatedCount, lastGeneratedAt, nextGenerationDate }, warnings?: string[] }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["recurring:write"],"x-plan-feature":"recurringInvoices"}},"/api/v1/recurring-schedules/{id}/recalculate":{"post":{"operationId":"post_recurring_schedules_id_recalculate","tags":["recurring-schedules"],"summary":"Recalculate the next generation date","description":"**Scope:** `recurring:write`\n\n**Response:** 200 { data: Schedule }\n\n- Resets nextGenerationDate, using a future start date if present, otherwise today. ENDED/ARCHIVED → 409. Audit RECURRING_SCHEDULE_RECALCULATED_VIA_API.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"200 { data: Schedule }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Schedule"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["recurring:write"]}},"/api/v1/documents":{"get":{"operationId":"get_documents","tags":["documents"],"summary":"List documents","description":"**Scope:** `documents:read`\n\n**Response:** { data: Document[], meta: { total, limit, offset, hasMore } }\n\n- Sorted by createdAt descending. File contents are never included.","parameters":[{"name":"type","in":"query","required":false,"schema":{"type":"string","enum":["INVOICE_PDF","RECEIPT","CONTRACT","OFFER","DELIVERY_NOTE","CORRESPONDENCE","TAX_DOCUMENT","OTHER"]}},{"name":"archived","in":"query","required":false,"description":"Default: false.","schema":{"type":"string"}},{"name":"invoice_id","in":"query","required":false,"schema":{"type":"string"}},{"name":"customer_id","in":"query","required":false,"schema":{"type":"string"}},{"name":"category_id","in":"query","required":false,"schema":{"type":"string"}},{"name":"search","in":"query","required":false,"description":"Name, description or tag.","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"schema":{"type":"number","default":50}},{"name":"offset","in":"query","required":false,"schema":{"type":"number","default":0}}],"responses":{"200":{"description":"{ data: Document[], meta: { total, limit, offset, hasMore } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Document"}},"meta":{"$ref":"#/components/schemas/Pagination"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["documents:read"]},"post":{"operationId":"post_documents","tags":["documents"],"summary":"Upload a document directly (Base64)","description":"**Scope:** `documents:write`\n\n**Response:** 201 { data: Document }\n\n- 400 FILE_TOO_LARGE above 20 MB; 400 MIME_NOT_ALLOWED outside the allowlist; 403 STORAGE_LIMIT when the plan's storage quota is exhausted.\n- Storage: S3 when STORAGE_PROVIDER=s3, otherwise Base64 in the database (502 STORAGE_FAILED on S3 error). Invoice/customer/category are checked (404/400). Audit DOCUMENT_CREATED_VIA_API.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"mimeType":{"type":"string","description":"Allowlist: PDF, PNG, JPEG, GIF, WebP, DOC(X), XLS(X), TXT, CSV."},"content":{"type":"string","format":"byte","description":"With or without a data: prefix; at most 20 MB."},"type":{"type":"string","enum":["INVOICE_PDF","RECEIPT","CONTRACT","OFFER","DELIVERY_NOTE","CORRESPONDENCE","TAX_DOCUMENT","OTHER"],"default":"OTHER"},"description":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}},"invoiceId":{"type":"string"},"customerId":{"type":"string"},"documentCategoryId":{"type":"string"}},"required":["name","mimeType","content"]}}}},"responses":{"201":{"description":"201 { data: Document }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Document"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["documents:write"]}},"/api/v1/documents/upload-url":{"post":{"operationId":"post_documents_upload_url","tags":["documents"],"summary":"Request a presigned S3 upload URL","description":"**Scope:** `documents:write`\n\n**Response:** 200 { data: { uploadUrl, key, method: \"PUT\", headers: { \"Content-Type\" }, expiresAt } }\n\n- 403 STORAGE_LIMIT if the file would exceed the plan storage quota.\n- Requires S3 storage, otherwise 400 S3_REQUIRED with a reference to POST /documents. URL valid for five minutes. Does not create a document yet.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"fileName":{"type":"string"},"name":{"type":"string"},"mimeType":{"type":"string"},"size":{"type":"number","description":"Bytes > 0, at most 100 MB."},"type":{"type":"string","enum":["INVOICE_PDF","RECEIPT","CONTRACT","OFFER","DELIVERY_NOTE","CORRESPONDENCE","TAX_DOCUMENT","OTHER"],"default":"OTHER"}},"required":["mimeType","size"]}}}},"responses":{"200":{"description":"200 { data: { uploadUrl, key, method: \"PUT\", headers: { \"Content-Type\" }, expiresAt } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["documents:write"]}},"/api/v1/documents/confirm":{"post":{"operationId":"post_documents_confirm","tags":["documents"],"summary":"Complete a direct upload and create the document","description":"**Scope:** `documents:write`\n\n**Response:** 201 { data: Document }\n\n- The storage quota uses the size of the S3 object, not the supplied size (which is overwritten). Exceeding the quota returns 403 STORAGE_LIMIT and deletes the uploaded object; a missing uploaded object returns 400 UPLOAD_NOT_FOUND.\n- Matches confirmDirectUpload in the dashboard. Without S3 → 400 S3_REQUIRED. Unlike the original plan, there is no {id} in the path because the document does not exist before confirmation.\n- Audit DOCUMENT_CREATED_VIA_API.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"key":{"type":"string","description":"From /upload-url; must start with the company ID, otherwise 400 INVALID_STORAGE_KEY."},"name":{"type":"string"},"mimeType":{"type":"string"},"size":{"type":"number"},"type":{"type":"string","enum":["INVOICE_PDF","RECEIPT","CONTRACT","OFFER","DELIVERY_NOTE","CORRESPONDENCE","TAX_DOCUMENT","OTHER"]},"description":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}},"invoiceId":{"type":"string"},"customerId":{"type":"string"},"documentCategoryId":{"type":"string"}},"required":["key","name","mimeType","size"]}}}},"responses":{"201":{"description":"201 { data: Document }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Document"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["documents:write"]}},"/api/v1/documents/{id}":{"get":{"operationId":"get_documents_id","tags":["documents"],"summary":"Read document metadata","description":"**Scope:** `documents:read`\n\n**Response:** { data: Document }\n\n- Metadata only.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"{ data: Document }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Document"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["documents:read"]},"put":{"operationId":"put_documents_id","tags":["documents"],"summary":"Update document metadata","description":"**Scope:** `documents:write`\n\n**Response:** { data: Document }\n\n- Metadata only; the file stays unchanged. References are checked against the company (404). Audit DOCUMENT_UPDATED_VIA_API.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"Not empty."},"description":{"type":"string"},"type":{"type":"string","enum":["INVOICE_PDF","RECEIPT","CONTRACT","OFFER","DELIVERY_NOTE","CORRESPONDENCE","TAX_DOCUMENT","OTHER"]},"tags":{"type":"array","items":{"type":"string"}},"invoiceId":{"type":"string","nullable":true},"customerId":{"type":"string","nullable":true},"documentCategoryId":{"type":"string","nullable":true}}}}}},"responses":{"200":{"description":"{ data: Document }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Document"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["documents:write"]},"delete":{"operationId":"delete_documents_id","tags":["documents"],"summary":"Delete a document","description":"**Scope:** `documents:write`\n\n**Response:** { data: { id, deleted: true } }\n\n- Also deletes the S3 file; an S3 error does not prevent deletion of the database record. Audit DOCUMENT_DELETED_VIA_API.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"{ data: { id, deleted: true } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["documents:write"]}},"/api/v1/documents/{id}/download":{"get":{"operationId":"get_documents_id_download","tags":["documents"],"summary":"Download document content","description":"**Scope:** `documents:read`\n\n**Response:** S3: { data: { id, name, mimeType, url, expiresAt } }. Local: binary data with Content-Disposition attachment.\n\n- Presigned S3 URL, valid for one hour. Missing data → 404 (DATA_MISSING/STORAGE_FAILED).","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"S3: { data: { id, name, mimeType, url, expiresAt } }. Local: binary data with Content-Disposition attachment.","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}},"application/octet-stream":{"schema":{"type":"string","format":"binary"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["documents:read"]}},"/api/v1/documents/{id}/archive":{"post":{"operationId":"post_documents_id_archive","tags":["documents"],"summary":"Archive a document","description":"**Scope:** `documents:write`\n\n**Response:** 200 { data: Document }\n\n- Sets isArchived=true, archivedAt=now. Already archived → 409. Audit DOCUMENT_ARCHIVED_VIA_API.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"200 { data: Document }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Document"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["documents:write"]}},"/api/v1/documents/{id}/unarchive":{"post":{"operationId":"post_documents_id_unarchive","tags":["documents"],"summary":"Restore a document from the archive","description":"**Scope:** `documents:write`\n\n**Response:** 200 { data: Document }\n\n- Sets isArchived=false, archivedAt=null. Not archived → 409. Audit DOCUMENT_ARCHIVED_VIA_API, with the direction in changes.isArchived.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"200 { data: Document }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Document"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["documents:write"]}},"/api/v1/exports":{"get":{"operationId":"get_exports","tags":["exports"],"summary":"Generate an accounting export (CSV, DATEV, BMD, UStVA)","description":"**Scope:** `exports:read`\n\n**Response:** File with Content-Disposition attachment. csv/ust: text/csv. datev: text/csv (posting batch). bmd: application/zip (buchungen.csv, personenkonten.csv, hinweise.txt). With Accept: application/json, csv returns { csv, filename, summary, warnings[] } instead.\n\n- Identical files to /dashboard/exports, with the same document selection: taxRelevantInvoiceConditions excludes drafts and proformas, and includes cancelled invoices only with a cancellation credit note.\n- Plan gate datevExport for type=datev/bmd (403); csv and ust are included in every plan. Invalid type → 400 with allowedValues. No AuditLog, matching the dashboard.","parameters":[{"name":"type","in":"query","required":false,"schema":{"type":"string","enum":["csv","datev","bmd","ust"],"default":"csv"}},{"name":"year","in":"query","required":false,"description":"Default: current year.","schema":{"type":"number"}},{"name":"month","in":"query","required":false,"description":"0–11.","schema":{"type":"number"}},{"name":"quarter","in":"query","required":false,"description":"1–4; without month/quarter, uses the full year.","schema":{"type":"number"}},{"name":"category_id","in":"query","required":false,"description":"also \"all\"/\"uncategorized\".","schema":{"type":"string"}},{"name":"category","in":"query","required":false,"description":"also \"all\"/\"uncategorized\".","schema":{"type":"string"}}],"responses":{"200":{"description":"File with Content-Disposition attachment. csv/ust: text/csv. datev: text/csv (posting batch). bmd: application/zip (buchungen.csv, personenkonten.csv, hinweise.txt). With Accept: application/json, csv returns { csv, filename, summary, warnings[] } instead.","content":{"application/json":{"schema":{"type":"object"}},"application/zip":{"schema":{"type":"string","format":"binary"}},"text/csv":{"schema":{"type":"string","format":"binary"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["exports:read"]}},"/api/v1/exports/sepa":{"post":{"operationId":"post_exports_sepa","tags":["exports"],"summary":"Create a SEPA batch and generate a direct debit file (PAIN.008)","description":"**Scope:** `direct-debits:write`\n\n**Response:** 200 by default: application/xml with Content-Disposition attachment and X-Sepa-Message-Id/-Transaction-Count/-Warnings/-Batch-Number headers. With Accept: application/json: { xml, filename, messageId, transactionCount, totalAmount, warnings[], batchId, batchNumber }.\n\n- Each call creates a batch (DD-YYYY-NNNN), as with the dashboard bulk action. This requires a dedicated scope rather than exports:read: its invoices are blocked from further exports until the batch is discarded (Dashboard → SEPA direct debits). The file remains stored. Collects the outstanding balance of sent or overdue invoices in euro; invoice status remains unchanged.\n- Skipped invoices appear in warnings (\"RE-… (Customer): Reason\"): wrong document type or status, no outstanding balance, invoice already in a batch that has not been discarded, missing or invalid IBAN, no active dated mandate, different mandate type, a used one-off mandate (OOFF), or a mandate reference outside SEPA rules (up to 35 letters, digits and + ? / - : ( ) . , ' spaces). X-Sepa-Warnings contains at most 50 entries and a note about the rest; use Accept: application/json for all warnings.\n- The company needs a valid sepaCreditorId (400 MISSING_CREDITOR_ID or INVALID_CREDITOR_ID) and a valid iban (400 MISSING_IBAN or INVALID_IBAN, including checksum). No eligible direct debits: 400 NO_VALID_TRANSACTIONS with details. None of the invoices found: 404. Audit: SEPA_EXPORT_VIA_API.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"invoiceIds":{"type":"array","items":{"type":"string"},"description":"1 to 500."},"requestedCollectionDate":{"type":"string","format":"date","description":"Must be after today (calendar day in Vienna)"},"mandateType":{"type":"string","enum":["CORE","B2B"],"default":"CORE","description":"Customers with a different mandate type are skipped."}},"required":["invoiceIds","requestedCollectionDate"]}}}},"responses":{"200":{"description":"200 by default: application/xml with Content-Disposition attachment and X-Sepa-Message-Id/-Transaction-Count/-Warnings/-Batch-Number headers. With Accept: application/json: { xml, filename, messageId, transactionCount, totalAmount, warnings[], batchId, batchNumber }.","content":{"application/json":{"schema":{"type":"object"}},"application/xml":{"schema":{"type":"string","format":"binary"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["direct-debits:write"]}},"/api/v1/exports/customers":{"get":{"operationId":"get_exports_customers","tags":["exports"],"summary":"Export customers as CSV","description":"**Scope:** `exports:read`\n\n**Response:** CSV with BOM, Content-Disposition attachment, header X-Export-Customer-Count.\n\n- The same 43 columns with German headers and formula neutralisation as the dashboard export. Audit CSV_EXPORT_CUSTOMERS, with apiKeyId added to changes.","parameters":[{"name":"include_archived","in":"query","required":false,"description":"Alias: `includeArchived`.","schema":{"type":"boolean","default":false}},{"name":"format","in":"query","required":false,"schema":{"type":"string","enum":["comma","semicolon"],"default":"semicolon"}}],"responses":{"200":{"description":"CSV with BOM, Content-Disposition attachment, header X-Export-Customer-Count.","content":{"text/csv":{"schema":{"type":"string","format":"binary"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["exports:read"]}},"/api/v1/company":{"get":{"operationId":"get_company","tags":["company"],"summary":"Read company details, number ranges and plan usage","description":"**Scope:** `company:read`\n\n**Response:** { data: { id, name, address, contact, legal, tax, bank, currency, paymentTerms, dunning, branding, numberRanges, plan: { plan, subscriptionPlan, subscriptionExpiresAt, trialEndsAt, features, limits }, usage: { documents, customers, emails, storage, apiCalls, users } } }\n\n- Read-only, no side effects.\n- Deliberately excluded: SMTP credentials, email domain/DKIM settings, Stripe IDs, finAPI connection, API keys, BMD account configuration, admin plan overrides and raw logo storage.","responses":{"200":{"description":"{ data: { id, name, address, contact, legal, tax, bank, currency, paymentTerms, dunning, branding, numberRanges, plan: { plan, subscriptionPlan, subscriptionExpiresAt, trialEndsAt, features, limits }, usage: { documents, customers, emails, storage, apiCalls, users } } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["company:read"]}},"/api/v1/webhooks":{"get":{"operationId":"get_webhooks","tags":["webhooks"],"summary":"List webhook endpoints","description":"**Scope:** `webhooks:read`\n**Plan feature:** `webhooks`\n\n**Response:** { data: [{ id, name, url, events[], is_active, max_retries, retry_delay_ms, rate_limit_per_min, total_deliveries, created_at, updated_at }] }\n\n- Enterprise plan gate (feature \"webhooks\"). Never returns the secret.","responses":{"200":{"description":"{ data: [{ id, name, url, events[], is_active, max_retries, retry_delay_ms, rate_limit_per_min, total_deliveries, created_at, updated_at }] }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"type":"object"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["webhooks:read"],"x-plan-feature":"webhooks"},"post":{"operationId":"post_webhooks","tags":["webhooks"],"summary":"Create a webhook endpoint","description":"**Scope:** `webhooks:write`\n**Plan feature:** `webhooks`\n\n**Response:** 201 { data: { id, name, url, secret, events[], is_active, max_retries, retry_delay_ms, created_at } } — the secret is returned only here\n\n- Audit WEBHOOK_ENDPOINT_CREATED. 400 for missing name/url/events, invalid event type or a URL failing the public HTTPS egress check: private/local host, embedded credentials or non-HTTPS.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"url":{"type":"string","description":"Must resolve to a public HTTPS host (assertPublicHttpUrl)"},"events":{"type":"array","items":{"type":"string"},"description":"Not empty; every value is checked against WebhookEventType, ignoring case and hyphens."},"secret":{"type":"string","description":"Default: generated as whsec_<48 hex>."},"max_retries":{"type":"number","default":3,"description":"Alias: `maxRetries`."},"retry_delay_ms":{"type":"number","default":1000,"description":"Alias: `retryDelayMs`."},"rate_limit_per_min":{"type":"number","nullable":true,"description":"Alias: `rateLimitPerMin`. Default: null (unlimited)."},"headers":{"type":"object","description":"Included with every delivery."},"metadata":{"type":"object"}},"required":["name","url","events"]}}}},"responses":{"201":{"description":"201 { data: { id, name, url, secret, events[], is_active, max_retries, retry_delay_ms, created_at } } — the secret is returned only here","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["webhooks:write"],"x-plan-feature":"webhooks"}},"/api/v1/webhooks/{id}":{"get":{"operationId":"get_webhooks_id","tags":["webhooks"],"summary":"Read a webhook endpoint and recent deliveries","description":"**Scope:** `webhooks:read`\n**Plan feature:** `webhooks`\n\n**Response:** { data: { id, name, url, events[], is_active, max_retries, retry_delay_ms, rate_limit_per_min, headers, metadata, total_deliveries, recent_deliveries: [...] (last ten), created_at, updated_at } }\n\n- 404 if not found or owned by another company.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"{ data: { id, name, url, events[], is_active, max_retries, retry_delay_ms, rate_limit_per_min, headers, metadata, total_deliveries, recent_deliveries: [...] (last ten), created_at, updated_at } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["webhooks:read"],"x-plan-feature":"webhooks"},"put":{"operationId":"put_webhooks_id","tags":["webhooks"],"summary":"Update a webhook endpoint","description":"**Scope:** `webhooks:write`\n**Plan feature:** `webhooks`\n\n**Response:** 200 { data: { id, name, url, events[], is_active, max_retries, retry_delay_ms, rate_limit_per_min, updated_at } } — rate_limit_per_min is new in the response\n\n- 404 if not found. 400 for an empty/invalid url, non-array events or an invalid event type.\n- Audit WEBHOOK_ENDPOINT_UPDATED with changes = { apiKeyId, <field>: { old, new } } for each field actually changed. Header changes are logged only as { old_keys, new_keys }; values are never logged because they can contain secrets such as Authorization tokens.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"description":"All optional/partial."},"url":{"description":"All optional/partial."},"events":{"description":"All optional/partial."},"is_active":{"type":"boolean","description":"Alias: `isActive`."},"max_retries":{"type":"number","description":"Alias: `maxRetries`."},"retry_delay_ms":{"type":"number","description":"Alias: `retryDelayMs`."},"rate_limit_per_min":{"type":"number","nullable":true,"description":"Alias: `rateLimitPerMin`."},"headers":{"type":"object"},"metadata":{"type":"object"}}}}}},"responses":{"200":{"description":"200 { data: { id, name, url, events[], is_active, max_retries, retry_delay_ms, rate_limit_per_min, updated_at } } — rate_limit_per_min is new in the response","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["webhooks:write"],"x-plan-feature":"webhooks"},"delete":{"operationId":"delete_webhooks_id","tags":["webhooks"],"summary":"Delete a webhook endpoint","description":"**Scope:** `webhooks:write`\n**Plan feature:** `webhooks`\n\n**Response:** 200 { success: true }\n\n- Hard delete, cascading WebhookDelivery rows through the foreign key. Audit WEBHOOK_ENDPOINT_DELETED. 404 if not found.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"200 { success: true }","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["webhooks:write"],"x-plan-feature":"webhooks"}},"/api/v1/webhooks/{id}/deliveries":{"get":{"operationId":"get_webhooks_id_deliveries","tags":["webhooks"],"summary":"List delivery attempts for a webhook endpoint","description":"**Scope:** `webhooks:read`\n**Plan feature:** `webhooks`\n\n**Response:** { data: [{ id, event_type, event_id, status, attempts, last_attempt_at, next_retry_at, response_status, response_body, response_time_ms, error_message, created_at, completed_at }], pagination: { total, limit, offset, has_more } }\n\n- 404 if the endpoint is not found. Invalid status/event_type → 400 with allowedValues, previously an empty result or possible 500.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}},{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["PENDING","SUCCESS","FAILED","RETRYING"]}},{"name":"event_type","in":"query","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"schema":{"type":"number","default":50}},{"name":"offset","in":"query","required":false,"schema":{"type":"number","default":0}}],"responses":{"200":{"description":"{ data: [{ id, event_type, event_id, status, attempts, last_attempt_at, next_retry_at, response_status, response_body, response_time_ms, error_message, created_at, completed_at }], pagination: { total, limit, offset, has_more } }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"type":"object"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["webhooks:read"],"x-plan-feature":"webhooks"}},"/api/v1/blog/posts":{"get":{"operationId":"get_blog_posts","tags":["blog"],"summary":"List blog posts","description":"**Scope:** `blog:read`\n\n**Response:** { posts: BlogPost[], pagination: { total, limit, offset } }","responses":{"200":{"description":"{ posts: BlogPost[], pagination: { total, limit, offset } }","content":{"application/json":{"schema":{"type":"object","required":["posts"],"properties":{"posts":{"type":"array","items":{"$ref":"#/components/schemas/BlogPost"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["blog:read"]},"post":{"operationId":"post_blog_posts","tags":["blog"],"summary":"Create a blog post","description":"**Scope:** `blog:write`\n\n**Response:** 201 BlogPost (without data envelope)\n\n- Creating with status: \"PUBLISHED\" additionally requires blog:publish.","responses":{"201":{"description":"201 BlogPost (without data envelope)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BlogPost"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["blog:write"]}},"/api/v1/blog/posts/{id}":{"get":{"operationId":"get_blog_posts_id","tags":["blog"],"summary":"Read a blog post","description":"**Scope:** `blog:read`\n\n**Response:** BlogPost (without data envelope)","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"BlogPost (without data envelope)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BlogPost"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["blog:read"]},"patch":{"operationId":"patch_blog_posts_id","tags":["blog"],"summary":"Update a blog post","description":"**Scope:** `blog:write`\n\n**Response:** BlogPost (without data envelope)\n\n- Transitions to or from PUBLISHED additionally require blog:publish.","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"BlogPost (without data envelope)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BlogPost"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["blog:write"]},"delete":{"operationId":"delete_blog_posts_id","tags":["blog"],"summary":"Delete a blog post","description":"**Scope:** `blog:write`\n\n**Response:** 200 { ok: true }","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"200 { ok: true }","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["blog:write"]}},"/api/v1/blog/posts/by-slug/{slug}":{"get":{"operationId":"get_blog_posts_by_slug_slug","tags":["blog"],"summary":"Read a blog post by slug","description":"**Scope:** `blog:read`\n\n**Response:** BlogPost (without data envelope)","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"BlogPost (without data envelope)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BlogPost"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["blog:read"]}},"/api/v1/blog/posts/{id}/publish":{"post":{"operationId":"post_blog_posts_id_publish","tags":["blog"],"summary":"Publish a blog post","description":"**Scope:** `blog:publish`\n\n**Response:** 200 BlogPost (without data envelope)","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"200 BlogPost (without data envelope)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BlogPost"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["blog:publish"]},"delete":{"operationId":"delete_blog_posts_id_publish","tags":["blog"],"summary":"Unpublish a blog post","description":"**Scope:** `blog:publish`\n\n**Response:** BlogPost (without data envelope)","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"BlogPost (without data envelope)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BlogPost"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["blog:publish"]}},"/api/v1/blog/posts/{id}/schedule":{"post":{"operationId":"post_blog_posts_id_schedule","tags":["blog"],"summary":"Schedule a blog post for a later time","description":"**Scope:** `blog:publish`\n\n**Response:** 200 BlogPost (without data envelope)","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"200 BlogPost (without data envelope)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BlogPost"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["blog:publish"]}},"/api/v1/blog/categories":{"get":{"operationId":"get_blog_categories","tags":["blog"],"summary":"List blog categories","description":"**Scope:** `blog:read`\n\n**Response:** { categories: BlogCategory[] }","responses":{"200":{"description":"{ categories: BlogCategory[] }","content":{"application/json":{"schema":{"type":"object","required":["categories"],"properties":{"categories":{"type":"array","items":{"$ref":"#/components/schemas/BlogCategory"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["blog:read"]},"post":{"operationId":"post_blog_categories","tags":["blog"],"summary":"Create a blog category","description":"**Scope:** `blog:write`\n\n**Response:** 201 { id, slug, name }","responses":{"201":{"description":"201 { id, slug, name }","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["blog:write"]}},"/api/v1/blog/categories/{id}":{"get":{"operationId":"get_blog_categories_id","tags":["blog"],"summary":"Read a blog category","description":"**Scope:** `blog:read`\n\n**Response:** BlogCategory (record without data envelope)","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"BlogCategory (record without data envelope)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BlogCategory"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["blog:read"]},"patch":{"operationId":"patch_blog_categories_id","tags":["blog"],"summary":"Update a blog category","description":"**Scope:** `blog:write`\n\n**Response:** BlogCategory (record without data envelope)","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"BlogCategory (record without data envelope)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BlogCategory"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["blog:write"]},"delete":{"operationId":"delete_blog_categories_id","tags":["blog"],"summary":"Delete a blog category","description":"**Scope:** `blog:write`\n\n**Response:** 200 { ok: true }","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"200 { ok: true }","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["blog:write"]}},"/api/v1/blog/tags":{"get":{"operationId":"get_blog_tags","tags":["blog"],"summary":"List blog tags","description":"**Scope:** `blog:read`\n\n**Response:** { tags: BlogTag[] }","responses":{"200":{"description":"{ tags: BlogTag[] }","content":{"application/json":{"schema":{"type":"object","required":["tags"],"properties":{"tags":{"type":"array","items":{"$ref":"#/components/schemas/BlogTag"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["blog:read"]},"post":{"operationId":"post_blog_tags","tags":["blog"],"summary":"Create a blog tag","description":"**Scope:** `blog:write`\n\n**Response:** 201 { id, slug, name }","responses":{"201":{"description":"201 { id, slug, name }","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["blog:write"]}},"/api/v1/blog/tags/{id}":{"get":{"operationId":"get_blog_tags_id","tags":["blog"],"summary":"Read a blog tag","description":"**Scope:** `blog:read`\n\n**Response:** BlogTag (record without data envelope)","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"BlogTag (record without data envelope)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BlogTag"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["blog:read"]},"patch":{"operationId":"patch_blog_tags_id","tags":["blog"],"summary":"Update a blog tag","description":"**Scope:** `blog:write`\n\n**Response:** BlogTag (record without data envelope)","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"BlogTag (record without data envelope)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BlogTag"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["blog:write"]},"delete":{"operationId":"delete_blog_tags_id","tags":["blog"],"summary":"Delete a blog tag","description":"**Scope:** `blog:write`\n\n**Response:** 200 { ok: true }","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"200 { ok: true }","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["blog:write"]}},"/api/v1/blog/media":{"get":{"operationId":"get_blog_media","tags":["blog"],"summary":"List the media library","description":"**Scope:** `blog:read`\n\n**Response:** { media: BlogMedia[] }","responses":{"200":{"description":"{ media: BlogMedia[] }","content":{"application/json":{"schema":{"type":"object","required":["media"],"properties":{"media":{"type":"array","items":{"$ref":"#/components/schemas/BlogMedia"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["blog:read"]},"post":{"operationId":"post_blog_media","tags":["blog"],"summary":"Upload media","description":"**Scope:** `blog:write`\n\n**Response:** 201 BlogMedia without width/height (without data envelope)","responses":{"201":{"description":"201 BlogMedia without width/height (without data envelope)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BlogMedia"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["blog:write"]}},"/api/v1/blog/media/{id}":{"get":{"operationId":"get_blog_media_id","tags":["blog"],"summary":"Read media","description":"**Scope:** `blog:read`\n\n**Response:** BlogMedia (without data envelope)","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"BlogMedia (without data envelope)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BlogMedia"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["blog:read"]}},"/api/v1/blog/ai/full-post":{"post":{"operationId":"post_blog_ai_full_post","tags":["blog"],"summary":"Start AI blog post generation","description":"**Scope:** `blog:write`\n\n**Response:** 200 { draft, ai: { model, promptHash } } — synchronous generation; use the job endpoints for long-running requests","responses":{"200":{"description":"200 { draft, ai: { model, promptHash } } — synchronous generation; use the job endpoints for long-running requests","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["blog:write"]}},"/api/v1/blog/ai/full-post/jobs":{"post":{"operationId":"post_blog_ai_full_post_jobs","tags":["blog"],"summary":"Create an AI generation job","description":"**Scope:** `blog:write`\n\n**Response:** 202 { job: Job }","responses":{"202":{"description":"202 { job: Job }","content":{"application/json":{"schema":{"type":"object","required":["job"],"properties":{"job":{"$ref":"#/components/schemas/Job"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["blog:write"]}},"/api/v1/blog/ai/full-post/jobs/{id}":{"get":{"operationId":"get_blog_ai_full_post_jobs_id","tags":["blog"],"summary":"Read an AI generation job's status","description":"**Scope:** `blog:read`\n\n**Response:** { job: Job }","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"{ job: Job }","content":{"application/json":{"schema":{"type":"object","required":["job"],"properties":{"job":{"$ref":"#/components/schemas/Job"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["blog:read"]}},"/api/v1/blog/ai/full-post/jobs/{id}/resume":{"post":{"operationId":"post_blog_ai_full_post_jobs_id_resume","tags":["blog"],"summary":"Resume an interrupted AI generation job","description":"**Scope:** `blog:write`\n\n**Response:** 202 { job: Job } (200 if the job has already completed)","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"responses":{"202":{"description":"202 { job: Job } (200 if the job has already completed)","content":{"application/json":{"schema":{"type":"object","required":["job"],"properties":{"job":{"$ref":"#/components/schemas/Job"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["blog:write"]}},"/api/v1/blog/posts/{id}/translations":{"post":{"operationId":"post_blog_posts_id_translations","tags":["blog"],"summary":"Create a translation draft in the other language","description":"**Scope:** `blog:write`\n\n**Response:** { id, locale, status }","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"responses":{"201":{"description":"{ id, locale, status }","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["blog:write"]}},"/api/v1/blog/posts/{id}/translation-review":{"get":{"operationId":"get_blog_posts_id_translation_review","tags":["blog"],"summary":"Read the translation review state and content hashes","description":"**Scope:** `blog:read`\n\n**Response:** { state, postHash, sourceHash, sourceId }","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"{ state, postHash, sourceHash, sourceId }","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["blog:read"]},"post":{"operationId":"post_blog_posts_id_translation_review","tags":["blog"],"summary":"Approve a reviewed translation against the current source content","description":"**Scope:** `blog:publish`\n\n**Response:** 200 { ok: true }","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"200 { ok: true }","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["blog:publish"]}},"/api/v1/blog/categories/{id}/translations":{"put":{"operationId":"put_blog_categories_id_translations","tags":["blog"],"summary":"Save the English name, slug and description of a category","description":"**Scope:** `blog:write`\n\n**Response:** 200 { id, categoryId, locale, slug, name, description }","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"200 { id, categoryId, locale, slug, name, description }","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["blog:write"]}},"/api/v1/blog/tags/{id}/translations":{"put":{"operationId":"put_blog_tags_id_translations","tags":["blog"],"summary":"Save the English name and slug of a tag","description":"**Scope:** `blog:write`\n\n**Response:** 200 { id, tagId, locale, slug, name }","parameters":[{"name":"id","in":"path","required":true,"description":"Record ID (cuid).","schema":{"type":"string"}}],"responses":{"200":{"description":"200 { id, tagId, locale, slug, name }","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[{"bearerAuth":[]}],"x-scopes":["blog:write"]}},"/api/v1/openapi.json":{"get":{"operationId":"get_openapi_json","tags":["meta"],"summary":"Load the machine-readable OpenAPI 3.0 document for the entire v1 API","description":"**Public** – no authentication required.\n\n**Response:** 200 application/json — OpenAPI 3.0.3 document generated from this catalogue (lib/api/openapi.ts).\n\n- Publicly accessible; no authentication required. Cache-Control: public, max-age=300.","parameters":[{"name":"locale","in":"query","required":false,"description":"Description language; technical fields and examples remain unchanged. Unsupported languages return 400.","schema":{"type":"string","enum":["de","en"],"default":"de"}}],"responses":{"200":{"description":"200 application/json — OpenAPI 3.0.3 document generated from this catalogue (lib/api/openapi.ts).","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"$ref":"#/components/responses/BadRequest"}},"security":[],"x-scopes":[]}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"API key in the Authorization header: `Authorization: Bearer sk_live_...`. Create one under Settings → API keys."}},"schemas":{"Error":{"type":"object","description":"Error response used by all v1 endpoints.","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable error message (German)."},"code":{"type":"string","description":"Machine-readable code, e.g. plan_no_access or quota_exceeded."},"allowedValues":{"type":"array","items":{"type":"string"},"description":"Allowed values when an enum value is invalid."},"requiredScopes":{"type":"array","items":{"type":"string"},"description":"Missing scopes for a 403 response."}}},"Pagination":{"type":"object","description":"Pagination information for list endpoints (under the meta key for some resources).","properties":{"total":{"type":"integer","description":"Total number of results."},"limit":{"type":"integer","description":"Page size."},"offset":{"type":"integer","description":"Offset."},"hasMore":{"type":"boolean","description":"Whether more pages are available (older resources also include has_more)."}}},"Success":{"type":"object","description":"Response from actions without a returned object.","properties":{"success":{"type":"boolean","enum":[true]}}},"Address":{"type":"object","description":"Company address (structured and free text).","properties":{"street":{"type":"string","nullable":true},"zipCode":{"type":"string","nullable":true},"city":{"type":"string","nullable":true},"country":{"type":"string","nullable":true},"countryCode":{"type":"string","nullable":true,"description":"ISO 3166-1 alpha-2 code."},"formatted":{"type":"string","nullable":true,"description":"Stored free text (Company.address)."}}},"CustomerRef":{"type":"object","description":"Embedded customer reference (short form).","properties":{"id":{"type":"string"},"name":{"type":"string"},"email":{"type":"string","nullable":true},"customerNumber":{"type":"string","nullable":true}}},"InvoiceLine":{"type":"object","description":"Invoice line. `discount` is an absolute amount, not a percentage.","properties":{"id":{"type":"string"},"position":{"type":"integer"},"description":{"type":"string"},"quantity":{"type":"number"},"unitPrice":{"type":"number","description":"Net unit price."},"taxRate":{"type":"number","description":"Tax rate as a percentage."},"discount":{"type":"number","description":"Absolute discount amount."},"lineType":{"type":"string","enum":["PRODUCT","SERVICE","DISCOUNT_PERCENT","DISCOUNT_ABSOLUTE","SHIPPING","ADVANCE_PAYMENT","CREDIT","TEXT","SUBTOTAL","PAGEBREAK"]},"lineTotal":{"type":"number"}}},"QuoteLine":{"type":"object","description":"Quote or order confirmation line. `discount` is a percentage (0–100), unlike invoice lines.","properties":{"id":{"type":"string"},"position":{"type":"integer"},"description":{"type":"string"},"quantity":{"type":"number"},"unit":{"type":"string"},"unitPrice":{"type":"number"},"taxRate":{"type":"number"},"discount":{"type":"number","description":"Percentage (0–100)."},"lineType":{"type":"string","enum":["PRODUCT","SERVICE","DISCOUNT_PERCENT","DISCOUNT_ABSOLUTE","SHIPPING","ADVANCE_PAYMENT","CREDIT","TEXT","SUBTOTAL","PAGEBREAK"]},"lineTotal":{"type":"number"}}},"OrderLine":{"type":"object","description":"Order line with delivery and billing progress.","properties":{"id":{"type":"string"},"position":{"type":"integer"},"description":{"type":"string"},"quantity":{"type":"number"},"unit":{"type":"string"},"unitPrice":{"type":"number"},"taxRate":{"type":"number"},"discount":{"type":"number","description":"Percentage (0–100)."},"discountMode":{"type":"string","enum":["percent"]},"lineType":{"type":"string","enum":["PRODUCT","SERVICE","DISCOUNT_PERCENT","DISCOUNT_ABSOLUTE","SHIPPING","ADVANCE_PAYMENT","CREDIT","TEXT","SUBTOTAL","PAGEBREAK"]},"lineTotal":{"type":"number"},"deliveredQty":{"type":"number"},"invoicedQty":{"type":"number"}}},"DeliveryNoteLine":{"type":"object","description":"Delivery note line without prices.","properties":{"id":{"type":"string"},"position":{"type":"integer"},"description":{"type":"string"},"quantity":{"type":"number"},"unit":{"type":"string"}}},"CreditNoteLine":{"type":"object","description":"Credit note line. Quantities and amounts are stored as negative values on the document.","properties":{"id":{"type":"string"},"position":{"type":"integer"},"description":{"type":"string"},"quantity":{"type":"number"},"unit":{"type":"string","nullable":true},"unit_price":{"type":"number"},"unitPrice":{"type":"number"},"tax_rate":{"type":"number"},"taxRate":{"type":"number"},"discount":{"type":"number"},"line_type":{"type":"string","nullable":true,"enum":["PRODUCT","SERVICE","DISCOUNT_PERCENT","DISCOUNT_ABSOLUTE","SHIPPING","ADVANCE_PAYMENT","CREDIT","TEXT","SUBTOTAL","PAGEBREAK",null]},"lineType":{"type":"string","nullable":true,"enum":["PRODUCT","SERVICE","DISCOUNT_PERCENT","DISCOUNT_ABSOLUTE","SHIPPING","ADVANCE_PAYMENT","CREDIT","TEXT","SUBTOTAL","PAGEBREAK",null]},"total":{"type":"number"}}},"ScheduleLine":{"type":"object","description":"Recurring invoice schedule line (JSON column).","required":["description","quantity","unitPrice","taxRate"],"properties":{"description":{"type":"string"},"quantity":{"type":"number"},"unitPrice":{"type":"number"},"taxRate":{"type":"number"},"discount":{"type":"number","description":"Absolute discount amount.","nullable":true},"lineType":{"type":"string","enum":["PRODUCT","SERVICE","DISCOUNT_PERCENT","DISCOUNT_ABSOLUTE","SHIPPING","ADVANCE_PAYMENT","CREDIT","TEXT","SUBTOTAL","PAGEBREAK"],"nullable":true},"articleNumber":{"type":"string","nullable":true}}},"CompanyUsageEntry":{"type":"object","description":"Plan usage for a single category.","properties":{"current":{"type":"integer"},"limit":{"type":"integer"},"remaining":{"type":"integer"}}},"Invoice":{"type":"object","description":"Invoice, proforma or credit note (distinguished by invoiceType). Most complete form: GET /api/v1/invoices/{id}.","properties":{"documentLocale":{"type":"string","enum":["de","en"],"description":"Stored document language, independent of the interface language."},"documentTextVersion":{"type":"integer","description":"Stored text revision of the document."},"id":{"type":"string"},"invoiceNumber":{"type":"string","description":"ENTWURF-… placeholder until issued."},"invoiceType":{"type":"string","enum":["INVOICE","CREDIT_NOTE","PROFORMA"]},"status":{"type":"string","enum":["DRAFT","FINAL","SENT","PAID","OVERDUE","CANCELLED"]},"documentCategoryId":{"type":"string","nullable":true},"issueDate":{"type":"string","format":"date-time"},"dueDate":{"type":"string","format":"date-time"},"servicePeriodStart":{"type":"string","format":"date-time","nullable":true},"servicePeriodEnd":{"type":"string","format":"date-time","nullable":true},"currency":{"type":"string","example":"EUR"},"netTotal":{"type":"number"},"taxTotal":{"type":"number"},"grossTotal":{"type":"number"},"reverseCharge":{"type":"boolean"},"taxExemptionReason":{"type":"string","enum":["NONE","SMALL_BUSINESS","REVERSE_CHARGE_EU","REVERSE_CHARGE_EU_NO_VAT","THIRD_COUNTRY","INTRA_COMMUNITY","EXPORT_DELIVERY","REVERSE_CHARGE_13B","INTERNAL_SUPPLY","OTHER_EXEMPTION"],"nullable":true},"taxExemptionNote":{"type":"string","nullable":true},"taxCountry":{"type":"string","nullable":true},"deliveryCountry":{"type":"string","nullable":true},"ossApplicable":{"type":"boolean"},"ossCountry":{"type":"string","nullable":true},"orderNumber":{"type":"string","nullable":true},"order_number":{"type":"string","nullable":true,"description":"snake_case alias of orderNumber."},"buyerReference":{"type":"string","nullable":true},"buyer_reference":{"type":"string","nullable":true,"description":"snake_case alias of buyerReference."},"e_invoice":{"type":"object","nullable":true,"description":"Factur-X/ZUGFeRD audit metadata (derived from Invoice.metadata).","properties":{"publicSectorRecipient":{"type":"string"},"publicSectorDepartment":{"type":"string"},"format":{"type":"string"},"submissionChannel":{"type":"string"},"submissionPortalUrl":{"type":"string"},"submissionReference":{"type":"string"},"submissionStatus":{"type":"string"},"submissionConfirmedAt":{"type":"string"},"submissionFeedback":{"type":"string"}}},"headerText":{"type":"string","nullable":true},"notes":{"type":"string","nullable":true},"footer":{"type":"string","nullable":true,"description":"Only in the detail response (GET /invoices/{id})."},"customTitle":{"type":"string","nullable":true},"pdfWithoutLetterhead":{"type":"boolean"},"invoiceDateIsServiceDate":{"type":"boolean"},"useShippingAddress":{"type":"boolean"},"shippingName":{"type":"string","nullable":true},"shippingStreet":{"type":"string","nullable":true},"shippingZipCode":{"type":"string","nullable":true},"shippingCity":{"type":"string","nullable":true},"shippingCountry":{"type":"string","nullable":true},"sentAt":{"type":"string","format":"date-time","nullable":true,"description":"Only in the detail response."},"paidAt":{"type":"string","format":"date-time","nullable":true,"description":"Only in the detail response."},"lockedAt":{"type":"string","format":"date-time","nullable":true,"description":"Only in the detail response."},"customer":{"description":"List responses contain only id/name/email; detail responses also include address and taxId.","type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"email":{"type":"string","nullable":true},"address":{"type":"string","nullable":true,"description":"Only in the detail response."},"taxId":{"type":"string","nullable":true,"description":"Only in the detail response."}}},"lines":{"type":"array","items":{"$ref":"#/components/schemas/InvoiceLine"}},"payments":{"type":"array","description":"Only in the detail response. A separate, smaller structure than the Payment schema from GET .../payments (date instead of payment_date, method instead of payment_method, without notes/createdAt).","items":{"type":"object","properties":{"id":{"type":"string"},"amount":{"type":"number"},"date":{"type":"string","format":"date-time"},"method":{"type":"string","enum":["BANK_TRANSFER","CASH","CREDIT_CARD","PAYPAL","OTHER"]},"reference":{"type":"string","nullable":true}}}},"paidAmount":{"type":"number","description":"Only in the detail response: total recorded payments."},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}},"example":{"id":"clx1invoice0001","invoiceNumber":"RE-2026-0042","invoiceType":"INVOICE","status":"SENT","currency":"EUR","netTotal":100,"taxTotal":20,"grossTotal":120,"customer":{"id":"clx1customer001","name":"Muster GmbH","email":"buchhaltung@muster.example"}}},"CreditNoteListItem":{"type":"object","description":"Credit note in the list response — without lines.","properties":{"documentLocale":{"type":"string","enum":["de","en"],"description":"Stored document language, independent of the interface language."},"documentTextVersion":{"type":"integer","description":"Stored text revision of the document."},"id":{"type":"string"},"invoice_number":{"type":"string"},"invoiceNumber":{"type":"string"},"numberIssued":{"type":"boolean","description":"false while the ENTWURF-… placeholder number applies."},"credit_note_reason":{"type":"string","enum":["CANCELLATION","RETURN","PRICE_CORRECTION","PAYOUT","COMMISSION","GOODWILL","OTHER"]},"creditNoteReason":{"type":"string","enum":["CANCELLATION","RETURN","PRICE_CORRECTION","PAYOUT","COMMISSION","GOODWILL","OTHER"]},"status":{"type":"string","enum":["DRAFT","FINAL","SENT","PAID","OVERDUE","CANCELLED"]},"issue_date":{"type":"string","format":"date-time"},"recipient":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"email":{"type":"string","nullable":true}}},"currency":{"type":"string"},"net_total":{"type":"number"},"tax_total":{"type":"number"},"gross_total":{"type":"number"},"notes":{"type":"string","nullable":true},"footer":{"type":"string","nullable":true},"metadata":{"type":"object","nullable":true},"payout":{"type":"object","nullable":true,"properties":{"id":{"type":"string"},"status":{"type":"string","enum":["PENDING","APPROVED","EXECUTED","FAILED","CANCELLED"]},"amount":{"type":"number"},"executed_at":{"type":"string","format":"date-time","nullable":true},"transaction_id":{"type":"string","nullable":true}}},"parent_invoice_id":{"type":"string","nullable":true},"created_at":{"type":"string","format":"date-time"}},"example":{"id":"clx1creditnote01","invoice_number":"GS-2026-0007","invoiceNumber":"GS-2026-0007","numberIssued":true,"credit_note_reason":"PAYOUT","creditNoteReason":"PAYOUT","status":"PAID","currency":"EUR","gross_total":-50}},"CreditNote":{"type":"object","description":"Credit note details (GET /api/v1/credit-notes/{id}).","properties":{"documentLocale":{"type":"string","enum":["de","en"],"description":"Stored document language, independent of the interface language."},"documentTextVersion":{"type":"integer","description":"Stored text revision of the document."},"id":{"type":"string"},"invoice_number":{"type":"string"},"invoiceNumber":{"type":"string"},"numberIssued":{"type":"boolean"},"credit_note_reason":{"type":"string","enum":["CANCELLATION","RETURN","PRICE_CORRECTION","PAYOUT","COMMISSION","GOODWILL","OTHER"]},"creditNoteReason":{"type":"string","enum":["CANCELLATION","RETURN","PRICE_CORRECTION","PAYOUT","COMMISSION","GOODWILL","OTHER"]},"status":{"type":"string","enum":["DRAFT","FINAL","SENT","PAID","OVERDUE","CANCELLED"]},"issue_date":{"type":"string","format":"date-time"},"due_date":{"type":"string","format":"date-time"},"recipient":{"type":"object","description":"Customer including bank details (for payment).","properties":{"id":{"type":"string"},"name":{"type":"string"},"email":{"type":"string","nullable":true},"address":{"type":"string","nullable":true},"iban":{"type":"string","nullable":true},"bic":{"type":"string","nullable":true},"bank_name":{"type":"string","nullable":true},"bank_account_holder":{"type":"string","nullable":true}}},"currency":{"type":"string"},"net_total":{"type":"number"},"tax_total":{"type":"number"},"gross_total":{"type":"number"},"reverse_charge":{"type":"boolean"},"tax_exemption_reason":{"type":"string","enum":["NONE","SMALL_BUSINESS","REVERSE_CHARGE_EU","REVERSE_CHARGE_EU_NO_VAT","THIRD_COUNTRY","INTRA_COMMUNITY","EXPORT_DELIVERY","REVERSE_CHARGE_13B","INTERNAL_SUPPLY","OTHER_EXEMPTION"],"nullable":true},"lines":{"type":"array","items":{"$ref":"#/components/schemas/CreditNoteLine"}},"notes":{"type":"string","nullable":true},"footer":{"type":"string","nullable":true},"metadata":{"type":"object","nullable":true},"parent_invoice":{"type":"object","nullable":true,"properties":{"id":{"type":"string"},"invoice_number":{"type":"string"},"gross_total":{"type":"number"}}},"payout":{"type":"object","nullable":true,"properties":{"id":{"type":"string"},"status":{"type":"string","enum":["PENDING","APPROVED","EXECUTED","FAILED","CANCELLED"]},"amount":{"type":"number"},"currency":{"type":"string"},"reference":{"type":"string","nullable":true},"iban":{"type":"string","nullable":true},"bic":{"type":"string","nullable":true},"bank_name":{"type":"string","nullable":true},"bank_account_holder":{"type":"string","nullable":true},"approved_at":{"type":"string","format":"date-time","nullable":true},"approved_by":{"type":"string","nullable":true},"executed_at":{"type":"string","format":"date-time","nullable":true},"transaction_id":{"type":"string","nullable":true},"failure_reason":{"type":"string","nullable":true},"metadata":{"type":"object","nullable":true},"created_at":{"type":"string","format":"date-time"}}},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"Customer":{"type":"object","description":"Customer. GET /customers/{id} also includes invoiceCount and recentInvoices (not part of this shared schema).","properties":{"documentLocale":{"type":"string","nullable":true,"enum":["de","en",null],"description":"Language preference for new documents; null inherits the company default."},"id":{"type":"string"},"customerNumber":{"type":"string","nullable":true},"name":{"type":"string"},"company":{"type":"string","description":"Compatibility alias of name (ChatCMM)."},"salutation":{"type":"string","nullable":true},"title":{"type":"string","nullable":true},"firstName":{"type":"string","nullable":true},"lastName":{"type":"string","nullable":true},"department":{"type":"string","nullable":true},"email":{"type":"string","nullable":true},"phone":{"type":"string","nullable":true},"mobile":{"type":"string","nullable":true},"website":{"type":"string","nullable":true},"taxId":{"type":"string","nullable":true},"tax_id":{"type":"string","nullable":true,"description":"Compatibility alias of taxId."},"taxIdValidated":{"type":"boolean"},"taxIdValidatedAt":{"type":"string","format":"date-time","nullable":true},"taxNumber":{"type":"string","nullable":true},"registryCourt":{"type":"string","nullable":true},"registryNumber":{"type":"string","nullable":true},"bmdAccountNumber":{"type":"string","nullable":true},"bmdRevenueAccount":{"type":"string","nullable":true},"datevId":{"type":"string","nullable":true},"street":{"type":"string","nullable":true},"zipCode":{"type":"string","nullable":true},"city":{"type":"string","nullable":true},"state":{"type":"string","nullable":true},"country":{"type":"string","nullable":true},"countryCode":{"type":"string","nullable":true},"shippingName":{"type":"string","nullable":true},"shippingCompany":{"type":"string","nullable":true},"shippingStreet":{"type":"string","nullable":true},"shippingZipCode":{"type":"string","nullable":true},"shippingCity":{"type":"string","nullable":true},"shippingState":{"type":"string","nullable":true},"shippingCountry":{"type":"string","nullable":true},"shippingCountryCode":{"type":"string","nullable":true},"address":{"type":"string","nullable":true,"description":"Stored free text, otherwise assembled from structured fields."},"shippingAddress":{"type":"string","nullable":true},"bankAccountHolder":{"type":"string","nullable":true},"bankName":{"type":"string","nullable":true},"iban":{"type":"string","nullable":true},"bic":{"type":"string","nullable":true},"sepaMandateReference":{"type":"string","nullable":true},"sepaMandateDate":{"type":"string","format":"date-time","nullable":true},"sepaMandateType":{"type":"string","enum":["CORE","B2B"],"nullable":true},"sepaMandateStatus":{"type":"string","enum":["ACTIVE","REVOKED","EXPIRED"],"nullable":true},"sepaSequenceType":{"type":"string","enum":["FRST","RCUR","OOFF","FNAL"],"nullable":true},"paymentTermDays":{"type":"integer","nullable":true},"discountPercent":{"type":"number","nullable":true},"discountDays":{"type":"integer","nullable":true},"defaultTaxExemptionReason":{"type":"string","enum":["NONE","SMALL_BUSINESS","REVERSE_CHARGE_EU","REVERSE_CHARGE_EU_NO_VAT","THIRD_COUNTRY","INTRA_COMMUNITY","EXPORT_DELIVERY","REVERSE_CHARGE_13B","INTERNAL_SUPPLY","OTHER_EXEMPTION"],"nullable":true},"defaultTaxExemptionNote":{"type":"string","nullable":true},"contacts":{"nullable":true,"description":"Arbitrary JSON (array or object)."},"billingPrefs":{"nullable":true,"description":"Arbitrary JSON."},"metadata":{"nullable":true,"description":"Arbitrary JSON."},"notes":{"type":"string","nullable":true},"category":{"type":"string","nullable":true},"customerSince":{"type":"string","format":"date-time","nullable":true},"isArchived":{"type":"boolean"},"archivedAt":{"type":"string","format":"date-time","nullable":true},"createdAt":{"type":"string","format":"date-time"},"created_at":{"type":"string","format":"date-time","description":"Compatibility alias of createdAt."},"updatedAt":{"type":"string","format":"date-time"}},"example":{"id":"clx1customer001","customerNumber":"K-1042","name":"Muster GmbH","email":"buchhaltung@muster.example","country":"Österreich","isArchived":false}},"Product":{"type":"object","description":"Product or service master record.","properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"type":"string","nullable":true},"sku":{"type":"string","nullable":true},"isService":{"type":"boolean"},"price":{"type":"number"},"taxRate":{"type":"number","description":"Tax rate as a percentage."},"unit":{"type":"string","example":"Stück"},"isActive":{"type":"boolean"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}},"example":{"id":"clx1product0001","name":"Beratung (Stunde)","isService":true,"price":120,"taxRate":20,"unit":"Stunde","isActive":true}},"Quote":{"type":"object","description":"Quote.","properties":{"documentLocale":{"type":"string","enum":["de","en"],"description":"Stored document language, independent of the interface language."},"documentTextVersion":{"type":"integer","description":"Stored text revision of the document."},"id":{"type":"string"},"quoteNumber":{"type":"string"},"status":{"type":"string","enum":["DRAFT","SENT","ACCEPTED","REJECTED","EXPIRED","CONVERTED"],"description":"Stored status (excluding EXPIRED)."},"displayStatus":{"type":"string","enum":["DRAFT","SENT","ACCEPTED","REJECTED","EXPIRED","CONVERTED","EXPIRED"],"description":"Displayed status including derived EXPIRED (never stored)."},"documentCategoryId":{"type":"string","nullable":true},"issueDate":{"type":"string","format":"date-time"},"validUntil":{"type":"string","format":"date-time"},"servicePeriodStart":{"type":"string","format":"date-time","nullable":true},"servicePeriodEnd":{"type":"string","format":"date-time","nullable":true},"currency":{"type":"string"},"netTotal":{"type":"number"},"taxTotal":{"type":"number"},"grossTotal":{"type":"number"},"reverseCharge":{"type":"boolean"},"headerText":{"type":"string","nullable":true},"notes":{"type":"string","nullable":true},"footer":{"type":"string","nullable":true},"convertedToInvoiceId":{"type":"string","nullable":true},"lockedAt":{"type":"string","format":"date-time","nullable":true},"currentVersion":{"type":"integer"},"customer":{"$ref":"#/components/schemas/CustomerRef"},"lines":{"type":"array","items":{"$ref":"#/components/schemas/QuoteLine"}},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"OrderConfirmation":{"type":"object","description":"Order confirmation (AB).","properties":{"documentLocale":{"type":"string","enum":["de","en"],"description":"Stored document language, independent of the interface language."},"documentTextVersion":{"type":"integer","description":"Stored text revision of the document."},"id":{"type":"string"},"confirmationNumber":{"type":"string"},"status":{"type":"string","enum":["DRAFT","SENT","CONFIRMED","IN_PROGRESS","COMPLETED","CANCELLED"]},"documentCategoryId":{"type":"string","nullable":true},"issueDate":{"type":"string","format":"date-time"},"deliveryDate":{"type":"string","format":"date-time","nullable":true},"validUntil":{"type":"string","format":"date-time","nullable":true},"currency":{"type":"string"},"netTotal":{"type":"number"},"taxTotal":{"type":"number"},"grossTotal":{"type":"number"},"reverseCharge":{"type":"boolean"},"notes":{"type":"string","nullable":true},"footer":{"type":"string","nullable":true},"deliveryTerms":{"type":"string","nullable":true},"paymentTerms":{"type":"string","nullable":true},"quoteId":{"type":"string","nullable":true},"convertedToInvoiceId":{"type":"string","nullable":true},"lockedAt":{"type":"string","format":"date-time","nullable":true},"currentVersion":{"type":"integer"},"customer":{"$ref":"#/components/schemas/CustomerRef"},"lines":{"type":"array","items":{"$ref":"#/components/schemas/QuoteLine"}},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"DeliveryNote":{"type":"object","description":"Delivery note without prices.","properties":{"documentLocale":{"type":"string","enum":["de","en"],"description":"Stored document language, independent of the interface language."},"documentTextVersion":{"type":"integer","description":"Stored text revision of the document."},"id":{"type":"string"},"deliveryNumber":{"type":"string"},"status":{"type":"string","enum":["DRAFT","SENT","DELIVERED","CONVERTED"]},"documentCategoryId":{"type":"string","nullable":true},"issueDate":{"type":"string","format":"date-time"},"deliveryDate":{"type":"string","format":"date-time","nullable":true},"shippingAddress":{"type":"string","nullable":true},"notes":{"type":"string","nullable":true},"orderId":{"type":"string","nullable":true},"convertedToInvoiceId":{"type":"string","nullable":true},"lockedAt":{"type":"string","format":"date-time","nullable":true},"currentVersion":{"type":"integer"},"customer":{"$ref":"#/components/schemas/CustomerRef"},"lines":{"type":"array","items":{"$ref":"#/components/schemas/DeliveryNoteLine"}},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"Order":{"type":"object","description":"Order. GET /orders/{id} also includes stats, invoices and deliveryNotes (not part of this shared schema).","properties":{"documentLocale":{"type":"string","enum":["de","en"],"description":"Stored document language, independent of the interface language."},"documentTextVersion":{"type":"integer","description":"Stored text revision of the document."},"id":{"type":"string"},"orderNumber":{"type":"string"},"name":{"type":"string"},"description":{"type":"string","nullable":true},"status":{"type":"string","enum":["DRAFT","ACTIVE","ON_HOLD","COMPLETED","CANCELLED"]},"documentCategoryId":{"type":"string","nullable":true},"startDate":{"type":"string","format":"date-time","nullable":true},"endDate":{"type":"string","format":"date-time","nullable":true},"currency":{"type":"string"},"totalBudget":{"type":"number","description":"Net total calculated from lines."},"notes":{"type":"string","nullable":true},"quoteId":{"type":"string","nullable":true},"orderConfirmationId":{"type":"string","nullable":true},"lockedAt":{"type":"string","format":"date-time","nullable":true},"currentVersion":{"type":"integer"},"customer":{"$ref":"#/components/schemas/CustomerRef"},"lines":{"type":"array","items":{"$ref":"#/components/schemas/OrderLine"}},"totals":{"type":"object","description":"Totals calculated from lines (lib/orders.ts computeOrderTotals).","properties":{"netTotal":{"type":"number"},"taxTotal":{"type":"number"},"grossTotal":{"type":"number"}}},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"Schedule":{"type":"object","description":"Recurring invoice schedule. GET /recurring-schedules/{id} also includes generatedInvoices.","properties":{"documentLocale":{"type":"string","enum":["de","en"],"description":"Stored document language, independent of the interface language."},"documentTextVersion":{"type":"integer","description":"Stored text revision of the document."},"id":{"type":"string"},"customerId":{"type":"string"},"customer":{"type":"object","nullable":true,"properties":{"id":{"type":"string"},"name":{"type":"string"},"email":{"type":"string","nullable":true}}},"documentCategoryId":{"type":"string","nullable":true},"name":{"type":"string","nullable":true},"description":{"type":"string","nullable":true},"status":{"type":"string","enum":["ACTIVE","PAUSED","ENDED","ARCHIVED"]},"frequency":{"type":"string","enum":["WEEKLY","BIWEEKLY","MONTHLY","QUARTERLY","SEMIANNUALLY","ANNUALLY"]},"intervalMultiplier":{"type":"integer"},"dayOfMonth":{"type":"integer","nullable":true},"dayOfWeek":{"type":"integer","nullable":true,"description":"0–6."},"useLastDayOfMonth":{"type":"boolean"},"startDate":{"type":"string","format":"date-time"},"endDate":{"type":"string","format":"date-time","nullable":true},"maxOccurrences":{"type":"integer","nullable":true},"createAsDraft":{"type":"boolean"},"autoSendEmail":{"type":"boolean"},"notifyOnCreate":{"type":"boolean"},"markAsPaid":{"type":"boolean"},"emailCc":{"type":"string","nullable":true},"emailBcc":{"type":"string","nullable":true},"paymentTermDays":{"type":"integer","nullable":true},"discountPercent":{"type":"number","nullable":true},"discountDays":{"type":"integer","nullable":true},"orderNumber":{"type":"string","nullable":true},"buyerReference":{"type":"string","nullable":true},"externalId":{"type":"string","nullable":true},"totalDiscountPercent":{"type":"number","nullable":true},"isOSS":{"type":"boolean"},"showServiceDateNotice":{"type":"boolean"},"taxExemptionReason":{"type":"string","enum":["NONE","SMALL_BUSINESS","REVERSE_CHARGE_EU","REVERSE_CHARGE_EU_NO_VAT","THIRD_COUNTRY","INTRA_COMMUNITY","EXPORT_DELIVERY","REVERSE_CHARGE_13B","INTERNAL_SUPPLY","OTHER_EXEMPTION"]},"taxExemptionNote":{"type":"string","nullable":true},"taxCountry":{"type":"string","nullable":true},"headerText":{"type":"string","nullable":true},"notes":{"type":"string","nullable":true},"footer":{"type":"string","nullable":true},"lines":{"type":"array","items":{"$ref":"#/components/schemas/ScheduleLine"}},"lastGeneratedAt":{"type":"string","format":"date-time","nullable":true},"nextGenerationDate":{"type":"string","format":"date-time","nullable":true},"generatedCount":{"type":"integer"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"generatedInvoices":{"type":"array","description":"Only in the detail response (GET /recurring-schedules/{id}).","items":{"type":"object","properties":{"id":{"type":"string"},"invoiceNumber":{"type":"string"},"status":{"type":"string"},"issueDate":{"type":"string","format":"date-time"},"grossTotal":{"type":"number"}}}}}},"Document":{"type":"object","description":"Document metadata (file storage). Download the file via GET .../download.","properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"type":"string","nullable":true},"type":{"type":"string","enum":["INVOICE_PDF","RECEIPT","CONTRACT","OFFER","DELIVERY_NOTE","CORRESPONDENCE","TAX_DOCUMENT","OTHER"]},"mimeType":{"type":"string"},"size":{"type":"integer","description":"File size in bytes."},"storageProvider":{"type":"string","enum":["LOCAL","S3"]},"tags":{"type":"array","items":{"type":"string"}},"isArchived":{"type":"boolean"},"archivedAt":{"type":"string","format":"date-time","nullable":true},"invoiceId":{"type":"string","nullable":true},"customerId":{"type":"string","nullable":true},"documentCategoryId":{"type":"string","nullable":true},"createdById":{"type":"string","nullable":true},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"Webhook":{"type":"object","description":"Webhook endpoint. secret is returned only once, in the POST /webhooks response.","properties":{"id":{"type":"string"},"name":{"type":"string"},"url":{"type":"string"},"secret":{"type":"string","description":"Only included in the POST /webhooks response."},"events":{"type":"array","items":{"type":"string","enum":["INVOICE_CREATED","INVOICE_UPDATED","INVOICE_SENT","INVOICE_PAID","INVOICE_OVERDUE","INVOICE_CANCELLED","CREDIT_NOTE_CREATED","CREDIT_NOTE_PAID","PAYOUT_APPROVED","PAYOUT_EXECUTED","PAYOUT_FAILED","PAYOUT_CANCELLED","CUSTOMER_CREATED","CUSTOMER_UPDATED","CUSTOMER_DELETED","REMINDER_SENT","REMINDER_ESCALATED","BLOG_POST_CREATED","BLOG_POST_UPDATED","BLOG_POST_PUBLISHED","BLOG_POST_UNPUBLISHED","BLOG_COMMENT_CREATED","BLOG_COMMENT_APPROVED"]}},"is_active":{"type":"boolean"},"max_retries":{"type":"integer"},"retry_delay_ms":{"type":"integer"},"rate_limit_per_min":{"type":"integer","nullable":true},"headers":{"type":"object","nullable":true,"description":"Only in the detail response."},"metadata":{"type":"object","nullable":true,"description":"Only in the detail response."},"total_deliveries":{"type":"integer"},"recent_deliveries":{"type":"array","description":"Last 10 delivery attempts, only in the detail response.","items":{"type":"object","properties":{"id":{"type":"string"},"event_type":{"type":"string","enum":["INVOICE_CREATED","INVOICE_UPDATED","INVOICE_SENT","INVOICE_PAID","INVOICE_OVERDUE","INVOICE_CANCELLED","CREDIT_NOTE_CREATED","CREDIT_NOTE_PAID","PAYOUT_APPROVED","PAYOUT_EXECUTED","PAYOUT_FAILED","PAYOUT_CANCELLED","CUSTOMER_CREATED","CUSTOMER_UPDATED","CUSTOMER_DELETED","REMINDER_SENT","REMINDER_ESCALATED","BLOG_POST_CREATED","BLOG_POST_UPDATED","BLOG_POST_PUBLISHED","BLOG_POST_UNPUBLISHED","BLOG_COMMENT_CREATED","BLOG_COMMENT_APPROVED"]},"status":{"type":"string","enum":["PENDING","SUCCESS","FAILED","RETRYING"]},"attempts":{"type":"integer"},"response_status":{"type":"integer","nullable":true},"created_at":{"type":"string","format":"date-time"},"completed_at":{"type":"string","format":"date-time","nullable":true}}}},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"Company":{"type":"object","description":"Company master data, number range configuration, plan features and usage (read-only).","properties":{"defaultDocumentLocale":{"type":"string","enum":["de","en"],"description":"Company default language for new documents."},"id":{"type":"string"},"name":{"type":"string"},"address":{"$ref":"#/components/schemas/Address"},"contact":{"type":"object","properties":{"phone":{"type":"string","nullable":true},"email":{"type":"string","nullable":true},"website":{"type":"string","nullable":true}}},"legal":{"type":"object","properties":{"registrationNumber":{"type":"string","nullable":true},"managingDirector":{"type":"string","nullable":true}}},"tax":{"type":"object","properties":{"vatId":{"type":"string","nullable":true},"taxNumber":{"type":"string","nullable":true},"isSmallBusiness":{"type":"boolean"},"ossRegistered":{"type":"boolean"},"ossRegistrationCountry":{"type":"string","nullable":true},"ossRegistrationNumber":{"type":"string","nullable":true}}},"bank":{"type":"object","properties":{"bankName":{"type":"string","nullable":true},"iban":{"type":"string","nullable":true},"bic":{"type":"string","nullable":true},"accountHolder":{"type":"string","nullable":true},"sepaCreditorId":{"type":"string","nullable":true},"paypalMeHandle":{"type":"string","nullable":true}}},"currency":{"type":"string"},"paymentTerms":{"type":"object","properties":{"paymentTermDays":{"type":"integer","nullable":true},"earlyPaymentDiscount":{"type":"number","nullable":true},"earlyPaymentDays":{"type":"integer","nullable":true}}},"dunning":{"type":"object","properties":{"reminderFee1":{"type":"number","nullable":true},"reminderFee2":{"type":"number","nullable":true},"reminderFee3":{"type":"number","nullable":true},"defaultInterestRate":{"type":"number","nullable":true}}},"branding":{"type":"object","properties":{"logoUrl":{"type":"string","nullable":true},"showClaribillBranding":{"type":"boolean"},"footerText":{"type":"string","nullable":true},"invoiceHeaderText":{"type":"string","nullable":true},"invoiceFooterText":{"type":"string","nullable":true}}},"numberRanges":{"type":"object","properties":{"invoiceNumberConfig":{"type":"object","nullable":true},"documentFilenameConfig":{"type":"object","nullable":true}}},"plan":{"type":"object","description":"adminPlanOverride is reflected in plan/features/limits but is never returned.","properties":{"plan":{"type":"string","description":"Effective plan including trial/admin overrides."},"subscriptionPlan":{"type":"string","enum":["FREE","STARTER","PROFESSIONAL","ENTERPRISE"]},"subscriptionExpiresAt":{"type":"string","format":"date-time","nullable":true},"trialEndsAt":{"type":"string","format":"date-time","nullable":true},"features":{"type":"object","additionalProperties":{"type":"boolean"}},"limits":{"type":"object","additionalProperties":{"type":"number"}}}},"usage":{"type":"object","properties":{"documents":{"$ref":"#/components/schemas/CompanyUsageEntry"},"customers":{"$ref":"#/components/schemas/CompanyUsageEntry"},"emails":{"$ref":"#/components/schemas/CompanyUsageEntry"},"storage":{"$ref":"#/components/schemas/CompanyUsageEntry"},"apiCalls":{"$ref":"#/components/schemas/CompanyUsageEntry"},"users":{"$ref":"#/components/schemas/CompanyUsageEntry"}}},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"Payment":{"type":"object","description":"Invoice payment. snake_case is primary, with camelCase aliases (lib/invoice-payments.ts).","properties":{"id":{"type":"string"},"amount":{"type":"number"},"payment_date":{"type":"string","format":"date-time"},"paymentDate":{"type":"string","format":"date-time"},"payment_method":{"type":"string","enum":["bank_transfer","cash","credit_card","paypal","other"],"description":"Lowercase."},"paymentMethod":{"type":"string","enum":["bank_transfer","cash","credit_card","paypal","other"],"description":"Lowercase."},"reference":{"type":"string","nullable":true},"notes":{"type":"string","nullable":true},"created_at":{"type":"string","format":"date-time"},"createdAt":{"type":"string","format":"date-time"}}},"BlogPost":{"type":"object","description":"Blog post. These routes do not wrap this object in { data: … }: lists return { posts: [...], pagination }; details return the bare object.","properties":{"locale":{"type":"string","enum":["de","en"]},"translationGroupId":{"type":"string"},"translationSourceId":{"type":"string","nullable":true},"translationSourceHash":{"type":"string","nullable":true},"translationReviewedAt":{"type":"string","format":"date-time","nullable":true},"featuredImageAlt":{"type":"string","nullable":true},"featuredImageCaption":{"type":"string","nullable":true},"id":{"type":"string"},"slug":{"type":"string"},"title":{"type":"string"},"excerpt":{"type":"string","nullable":true},"content":{"type":"string","description":"Sanitised HTML."},"status":{"type":"string","enum":["DRAFT","SCHEDULED","PUBLISHED","ARCHIVED"]},"publishedAt":{"type":"string","format":"date-time","nullable":true},"scheduledFor":{"type":"string","format":"date-time","nullable":true},"author":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string","nullable":true},"email":{"type":"string","nullable":true}}},"featuredImage":{"type":"object","nullable":true,"properties":{"id":{"type":"string"},"url":{"type":"string"},"alt":{"type":"string","nullable":true}}},"ogImage":{"type":"object","nullable":true,"properties":{"id":{"type":"string"},"url":{"type":"string"},"alt":{"type":"string","nullable":true}}},"metaTitle":{"type":"string","nullable":true},"metaDescription":{"type":"string","nullable":true},"canonical":{"type":"string","nullable":true},"aiGenerated":{"type":"boolean"},"aiModel":{"type":"string","nullable":true},"aiPromptHash":{"type":"string","nullable":true},"categories":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"slug":{"type":"string"},"name":{"type":"string"}}}},"tags":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"slug":{"type":"string"},"name":{"type":"string"}}}},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"url":{"type":"string","description":"Relative path /blog/{slug} or /en/blog/{slug}, matching the post language."}}},"BlogCategory":{"type":"object","description":"Blog category. POST responses contain only id/slug/name (a subset of this schema).","properties":{"id":{"type":"string"},"slug":{"type":"string"},"name":{"type":"string"},"description":{"type":"string","nullable":true,"description":"Only in the list response."},"parentId":{"type":"string","nullable":true,"description":"Only in the list response."},"postCount":{"type":"integer","description":"Only in the list response."}}},"BlogTag":{"type":"object","description":"Blog tag. POST responses contain only id/slug/name (a subset of this schema).","properties":{"id":{"type":"string"},"slug":{"type":"string"},"name":{"type":"string"},"postCount":{"type":"integer","description":"Only in the list response."}}},"BlogMedia":{"type":"object","description":"Media library image. width/height are only included in the list response.","properties":{"id":{"type":"string"},"url":{"type":"string"},"alt":{"type":"string","nullable":true},"mimeType":{"type":"string"},"bytes":{"type":"integer"},"width":{"type":"integer","nullable":true,"description":"Only in the list response."},"height":{"type":"integer","nullable":true,"description":"Only in the list response."},"createdAt":{"type":"string","format":"date-time"}}},"Job":{"type":"object","description":"Asynchronous AI blog post generation job. Returned under { job: … }, not { data: … }.","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["QUEUED","RUNNING","SUCCEEDED","FAILED"]},"statusUrl":{"type":"string","description":"Relative path to GET .../jobs/{id}."},"result":{"nullable":true,"description":"Generated blog post draft (arbitrary JSON), only when status is SUCCEEDED."},"ai":{"type":"object","nullable":true,"description":"Only when status is SUCCEEDED.","properties":{"model":{"type":"string","nullable":true},"promptHash":{"type":"string","nullable":true}}},"error":{"type":"string","nullable":true,"description":"Only when status is FAILED."},"attempts":{"type":"integer"},"lockedAt":{"type":"string","format":"date-time","nullable":true},"completedAt":{"type":"string","format":"date-time","nullable":true},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}}},"responses":{"BadRequest":{"description":"Invalid input: required field, enum value, number/date format or business rule.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Ungültiger Status","allowedValues":["DRAFT","FINAL","SENT"]}}}},"Unauthorized":{"description":"Authorization header missing, or API key invalid or disabled.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Unauthorized"}}}},"Forbidden":{"description":"Missing scope, plan without API access (code \"plan_no_access\"), or plan limit exhausted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Fehlende Berechtigung","requiredScopes":["invoices:write"]}}}},"NotFound":{"description":"Record does not exist or belongs to another company.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Nicht gefunden"}}}},"Conflict":{"description":"State conflict: incorrect status for the action, concurrent modification or number already in use.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Die Rechnung ist bereits versendet"}}}},"TooManyRequests":{"description":"Monthly quota exhausted (code \"quota_exceeded\") or rate limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Monatliches API-Kontingent erschöpft","code":"quota_exceeded"}}}}}}}