{"components":{"schemas":{"ErrorEnvelope":{"example":{"error":"Missing required field `toEmail`","success":false},"properties":{"details":{},"error":{"type":"string"},"success":{"const":false,"type":"boolean"}},"required":["success","error"],"type":"object"},"SuccessEnvelope":{"example":{"data":{},"success":true},"properties":{"data":{},"success":{"const":true,"type":"boolean"}},"required":["success"],"type":"object"}},"securitySchemes":{"ApiKeyHeader":{"description":"Magic Heidi API key.","in":"header","name":"X-API-Key","type":"apiKey"}}},"info":{"description":"Magic Heidi Invoicing Software API for creating clients and invoices, generating PDFs, sending invoices, and recording payments.","title":"Magic Heidi Invoicing Software API","version":"1.0.0"},"openapi":"3.1.0","paths":{"/backups":{"post":{"description":"Generates a backup archive containing all invoices, expenses, and receipts for the specified year. Optionally sends the backup to an email address.","requestBody":{"content":{"application/json":{"examples":{"archiveYear":{"summary":"Backup archived year","value":{"year":2022}},"explicitEmail":{"summary":"Backup with explicit email","value":{"email":"owner@example.com","year":2025}},"lastYear":{"summary":"Backup previous year","value":{"year":2025}},"twoYearsAgo":{"summary":"Backup older year","value":{"year":2024}}},"schema":{"properties":{"email":{"type":"string"},"year":{"type":"integer"}},"required":["year"],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"examples":{"backupTriggered":{"summary":"Backup export started","value":{"data":{"function":"create_backup","raw":{"result":true},"result":true,"status":200},"success":true}}}}}},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}}}},"description":"Bad Request"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}}}},"description":"Unauthorized"},"429":{"content":{"application/json":{"examples":{"rateLimited":{"summary":"Rate limited","value":{"details":{"response":{"error":{"message":"Too many requests! Please try again later","status":"RESOURCE_EXHAUSTED"}},"status":429},"error":"Too many requests! Please try again later","success":false}}}}},"description":"Too Many Requests"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"},"503":{"content":{"application/json":{"examples":{"temporarilyUnavailable":{"summary":"Service temporarily unavailable","value":{"error":"This action is temporarily unavailable.","success":false}}}}},"description":"Service Unavailable"}},"summary":"Export yearly backup","tags":["Backups"]}},"/bank-accounts":{"get":{"responses":{"200":{"description":"OK"},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}}}},"description":"Bad Request"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}}}},"description":"Unauthorized"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"}},"summary":"List invoice payment accounts","tags":["Bank Accounts"]},"post":{"description":"Creates an IBAN or custom account in company.banks.","requestBody":{"content":{"application/json":{"examples":{"custom":{"value":{"currency":"USD","data":"Routing 021000021 / Account 123456","name":"USD account","type":"custom"}},"iban":{"value":{"currency":"CHF","iban":"CH0700700112900411647","name":"Default","type":"iban"}}},"schema":{"type":"object"}}},"required":true},"responses":{"201":{"description":"Created"},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}}}},"description":"Bad Request"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}}}},"description":"Unauthorized"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"}},"summary":"Create an invoice payment account","tags":["Bank Accounts"]}},"/bank-accounts/{bankAccountId}":{"delete":{"responses":{"200":{"description":"Deleted"},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}}}},"description":"Bad Request"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}}}},"description":"Unauthorized"},"409":{"description":"Account is referenced by invoices"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"}},"summary":"Delete an unused invoice payment account","tags":["Bank Accounts"]},"get":{"responses":{"200":{"description":"OK"},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}}}},"description":"Bad Request"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}}}},"description":"Unauthorized"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"}},"summary":"Get an invoice payment account","tags":["Bank Accounts"]},"parameters":[{"in":"path","name":"bankAccountId","required":true,"schema":{"type":"string"}}],"patch":{"responses":{"200":{"description":"Updated"},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}}}},"description":"Bad Request"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}}}},"description":"Unauthorized"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"}},"summary":"Update an invoice payment account","tags":["Bank Accounts"]}},"/bank/connect":{"post":{"description":"Starts a bank connection for the authenticated user and returns the `authorizationUri` you must open in the user's browser so they can log into their bank and grant consent.\n\nFlow: (1) call this with the `providerId` chosen from `GET /bank/providers`; (2) redirect/open the returned `authorizationUri` \u2014 the user authenticates and consents at their own bank (we never see their bank credentials); (3) after they return, call `POST /bank/fetch`, which confirms the consent and returns their transactions.\n\nA pending connection record is stored immediately; it becomes `active` on the first successful fetch. `providerId` defaults to the SIX test simulator when omitted.","requestBody":{"content":{"application/json":{"examples":{"connect":{"summary":"Connect a specific bank","value":{"providerId":"IIDX00761"}}},"schema":{"properties":{"providerId":{"description":"Bank id from GET /bank/providers. Defaults to the SIX test simulator if omitted.","type":"string"},"scope":{"default":"urn:blink:xs2a:ais","description":"bLink consent scope. Defaults to `urn:blink:xs2a:ais` (read account information).","type":"string"}},"type":"object"}}},"required":false},"responses":{"201":{"content":{"application/json":{"examples":{"connect":{"summary":"Consent link + connection id","value":{"data":{"authorizationUri":"https://tools-blink-xe.np.six-group.com/serviceprovider/api/consent-flow/v2/oauth/authorize?response_type=code&redirect_uri=...&provider_id=IIDX00761&scope=urn:blink:xs2a:ais","connectionId":"3f2c9b7a-1d4e-4c88-9a11-0b6d2e7c5a90","permissionId":"8bb4641a-5633-4f87-b46b-b3e201553eaa"},"success":true}}},"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}},"description":"Connection started; redirect the user to `authorizationUri`"},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}}}},"description":"Bad Request"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}},"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}},"description":"Invalid API key"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"},"502":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}},"description":"bLink could not create the consent"}},"summary":"Start a bank connection (get the consent link)","tags":["Bank Connections"]}},"/bank/connections":{"get":{"description":"Returns the authenticated user's bank connections and their `status` (`pending` until the user completes consent, then `active`), along with the accounts discovered on the last fetch.","responses":{"200":{"content":{"application/json":{"examples":{"connections":{"summary":"One active connection","value":{"data":[{"accounts":[{"currency":"CHF","designation":"Bank Account 1","iban":"CH0208401000051138778","id":"1","owner":"Hans Muster"}],"createdAt":"2026-07-17T14:05:00Z","id":"3f2c9b7a-1d4e-4c88-9a11-0b6d2e7c5a90","lastSyncedAt":"2026-07-17T14:06:12Z","permissionId":"8bb4641a-5633-4f87-b46b-b3e201553eaa","providerId":"IIDX00761","scope":"urn:blink:xs2a:ais","status":"active"}],"success":true}}},"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}},"description":"The user's bank connections"},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}}}},"description":"Bad Request"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}},"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}},"description":"Invalid API key"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"}},"summary":"List the user's bank connections","tags":["Bank Connections"]}},"/bank/connections/{connectionId}":{"delete":{"description":"Revokes the consent at SIX bLink and deletes the connection. The connection is removed locally even if the revoke fails (an expired or already-revoked consent); `revoked` reports whether SIX confirmed it.","parameters":[{"description":"The `id` from `GET /bank/connections`.","in":"path","name":"connectionId","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"examples":{"disconnected":{"summary":"Consent revoked and connection removed","value":{"data":{"deleted":true,"id":"5f1a...","revoked":true},"success":true}}},"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}},"description":"Connection disconnected"},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}}}},"description":"Bad Request"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}},"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}},"description":"Invalid API key"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}},"description":"Bank connection not found"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"}},"summary":"Disconnect a bank","tags":["Bank Connections"]}},"/bank/fetch":{"post":{"description":"Finalizes any pending connections (confirming consent), fetches the latest transactions across the user's connected accounts, matches incoming payments against unpaid invoices, turns unmatched acquirer/platform payouts (SumUp, Stripe, Apple, \u2026) into paid invoices, and turns outgoing ones into paid expenses.\n\nCreated expenses are named and categorised automatically, in the user's language. Categories are only ever chosen from the ones the user already has \u2014 none are invented \u2014 so `needCategory` counts the expenses nothing fitted, the only bank work left to do by hand. The amount, date and currency always come from the bank. Ordinary unmatched customer credits are never classified by AI on this unattended path.\n\nAlongside the counts, `details` carries the rows behind them \u2014 the same buckets-of-rows shape the spreadsheet importers return, so a sync summary can be opened up instead of being six numbers. Buckets: `matched` (credits applied to an invoice, with `invoiceId`), `invoices_created` (payout credits booked as paid invoices, with `invoiceId`), `expenses_created` (debits booked as expenses, with `expenseId`), `unmatched_credits` (credits with no invoice \u2014 these carry the machine-readable `date`/`amount` pair so a client can offer to create one, and name the suspected invoice when there was a weak match), `already_imported`, `pending` (not booked yet) and `skipped_connections` (with the reason nothing was fetched).\n\nEach bucket is capped at 200 rows; the counts are never capped.","responses":{"200":{"content":{"application/json":{"examples":{"fetch":{"summary":"Counts plus the rows behind them","value":{"data":{"details":{"already_imported":[],"expenses_created":[{"expenseId":"a450...","label":"Swisscom Mobile-Abo August","reason":"Categorised as B\u00fcroausgaben","sublabel":"2026-08-20 \u00b7 -89.99 CHF"}],"invoices_created":[{"invoiceId":"9a1b...","label":"SUMUP PAYMENTS LIMITED","reason":"Created paid invoice \u00b7 Sumup payout 20.08.2026","sublabel":"2026-08-20 \u00b7 +39.40 CHF"}],"matched":[{"invoiceId":"6c2f...","label":"Muster AG","reason":"Paid Muster AG \u00b7 exact amount + reference","sublabel":"2026-08-20 \u00b7 +1500.00 CHF \u00b7 RF18 5390 0754 7034"}],"pending":[],"skipped_connections":[],"unmatched_credits":[{"amount":450.5,"date":"2026-08-20","label":"Incoming transfer","reason":"No matching invoice","sublabel":"2026-08-20 \u00b7 +450.50 CHF"}]},"expensesCreated":1,"invoicesCreated":1,"invoicesMatched":1,"needCategory":0,"partialPayments":0,"skippedConnections":0,"syncedConnections":1,"unmatchedCredits":2},"success":true}}},"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}},"description":"Transactions fetched"},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}}}},"description":"Bad Request"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}},"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}},"description":"Invalid API key"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"},"502":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}},"description":"bLink fetch failed"}},"summary":"Fetch the user's latest transactions","tags":["Bank Connections"]}},"/bank/providers":{"get":{"description":"Returns the directory of banks (providers) registered on SIX bLink. Use this to build a bank picker: take the `providerId` of the bank the user selects and pass it to `POST /bank/connect` to start the consent flow.\n\nEach item has `providerId` (the id to connect with), `name`, `abbreviation`, `url`, and `logo`. `logo` is `null` unless `?logos=true` is set, in which case it is a `data:` URI (SVG) suitable for direct display.","parameters":[{"description":"When true, include each bank's logo as a data: URI in `logo`.","in":"query","name":"logos","required":false,"schema":{"default":false,"type":"boolean"}}],"responses":{"200":{"content":{"application/json":{"examples":{"providers":{"summary":"Directory of banks","value":{"data":[{"abbreviation":"AKB","logo":null,"name":"Aargauische Kantonalbank","providerId":"IIDX00761","url":"https://www.akb.ch/"},{"abbreviation":"CIC","logo":null,"name":"Bank CIC","providerId":"IIDX08710","url":"https://www.cic.ch/"}],"success":true}}},"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}},"description":"List of connectable banks"},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}}}},"description":"Bad Request"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}},"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}},"description":"Invalid API key"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"},"502":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}},"description":"bLink directory unavailable"}},"summary":"List banks available to connect","tags":["Bank Connections"]}},"/categories":{"get":{"description":"Returns all categories (both expense categories and payment methods), sorted by `name`.\n\nNo query parameters. Use `/expense-categories` or `/payment-methods` for type-specific endpoints, or filter the response client-side by the `type` field.","responses":{"200":{"content":{"application/json":{"examples":{"empty":{"summary":"No results","value":{"data":{"count":0,"items":[]},"success":true}},"listMany":{"summary":"Multiple results","value":{"data":{"count":2,"items":[{"emoji":"\ud83d\ude86","id":"cat_travel","name":"Travel","type":"expense"},{"emoji":"\ud83c\udfe6","id":"pm_bank","name":"Bank transfer","type":"payment"}]},"success":true}},"listOne":{"summary":"One result","value":{"data":{"count":1,"items":[{"emoji":"\ud83d\ude86","id":"cat_travel","name":"Travel","type":"expense"}]},"success":true}}}}}},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}}}},"description":"Bad Request"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}}}},"description":"Unauthorized"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"}},"summary":"List categories","tags":["Categories"]},"post":{"requestBody":{"content":{"application/json":{"examples":{"cashPayment":{"summary":"Cash payment category","value":{"emoji":"\ud83d\udcb5","name":"Cash","type":"payment"}},"expenseCategory":{"summary":"Expense category","value":{"emoji":"\ud83d\ude86","name":"Travel","type":"expense"}},"paymentCategory":{"summary":"Payment method category","value":{"emoji":"\ud83c\udfe6","name":"Bank transfer","type":"payment"}},"softwareExpense":{"summary":"Software expense category","value":{"emoji":"\ud83d\udcbb","name":"Software","type":"expense"}}},"schema":{"type":"object"}}},"required":true},"responses":{"201":{"content":{"application/json":{"examples":{"created":{"summary":"Created","value":{"data":{"emoji":"\ud83d\ude86","id":"cat_travel","name":"Travel","type":"expense"},"success":true}}}}}},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}}}},"description":"Bad Request"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}}}},"description":"Unauthorized"},"409":{"content":{"application/json":{"examples":{"alreadyExists":{"summary":"Document ID already exists","value":{"error":"Document 'abc123' already exists","success":false}}}}},"description":"Conflict"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"}},"summary":"Create category","tags":["Categories"]}},"/categories/{id}":{"delete":{"responses":{"200":{"content":{"application/json":{"examples":{"deleted":{"summary":"Deleted","value":{"data":{"deleted":true,"id":"cat_travel"},"success":true}}}}}},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}}}},"description":"Bad Request"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"examples":{"notFound":{"summary":"Document not found","value":{"error":"Document not found","success":false}}}}},"description":"Not Found"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"}},"summary":"Delete category","tags":["Categories"]},"get":{"responses":{"200":{"content":{"application/json":{"examples":{"found":{"summary":"Document found","value":{"data":{"emoji":"\ud83d\ude86","id":"cat_travel","name":"Travel","type":"expense"},"success":true}}}}}},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}}}},"description":"Bad Request"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"examples":{"notFound":{"summary":"Document not found","value":{"error":"Document not found","success":false}}}}},"description":"Not Found"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"}},"summary":"Get category","tags":["Categories"]},"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"patch":{"requestBody":{"content":{"application/json":{"examples":{"changeEmoji":{"summary":"Change emoji","value":{"emoji":"\u2708\ufe0f"}},"rename":{"summary":"Rename category","value":{"name":"Travel & Transport"}},"switchType":{"summary":"Switch category type","value":{"type":"expense"}},"updateBoth":{"summary":"Update name and emoji","value":{"emoji":"\ud83c\udfe6","name":"Bank transfer"}}},"schema":{"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"examples":{"updated":{"summary":"Updated","value":{"data":{"emoji":"\ud83d\ude86","id":"cat_travel","name":"Travel","type":"expense"},"success":true}}}}}},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}}}},"description":"Bad Request"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"examples":{"notFound":{"summary":"Document not found","value":{"error":"Document not found","success":false}}}}},"description":"Not Found"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"}},"summary":"Update category","tags":["Categories"]}},"/clients":{"get":{"description":"Returns all of the user's clients, sorted alphabetically by `name`.\n\nNo query parameters. Filter the response client-side if needed.","responses":{"200":{"content":{"application/json":{"examples":{"empty":{"summary":"No results","value":{"data":{"count":0,"items":[]},"success":true}},"listMany":{"summary":"Multiple results","value":{"data":{"count":2,"items":[{"buildingNumber":"12","ccEmails":["finance@acme.ch"],"city":"Z\u00fcrich","country":"CH","email":"billing@acme.ch","files":[],"id":"client_123","name":"ACME GmbH","notes":"","street":"Bahnhofstrasse","zip":"8001"},{"buildingNumber":"5","ccEmails":[],"city":"Bern","country":"CH","email":"max@example.com","files":[],"id":"client_private_1","name":"Max Muster","notes":"","street":"Musterweg","zip":"3007"}]},"success":true}},"listOne":{"summary":"One result","value":{"data":{"count":1,"items":[{"buildingNumber":"12","ccEmails":["finance@acme.ch"],"city":"Z\u00fcrich","country":"CH","email":"billing@acme.ch","files":[],"id":"client_123","name":"ACME GmbH","notes":"","street":"Bahnhofstrasse","zip":"8001"}]},"success":true}}}}}},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}}}},"description":"Bad Request"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}}}},"description":"Unauthorized"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"}},"summary":"List clients","tags":["Clients"]},"post":{"requestBody":{"content":{"application/json":{"examples":{"businessClient":{"summary":"Business client","value":{"buildingNumber":"12","city":"Z\u00fcrich","country":"CH","email":"billing@acme.ch","name":"ACME GmbH","street":"Bahnhofstrasse","zip":"8001"}},"clientWithCc":{"summary":"Client with CC emails","value":{"buildingNumber":"12","ccEmails":["finance@acme.ch"],"city":"Z\u00fcrich","country":"CH","email":"billing@acme.ch","name":"ACME GmbH","street":"Bahnhofstrasse","zip":"8001"}},"clientWithNotes":{"summary":"Client with notes","value":{"ccEmails":["finance@acme.ch","cfo@acme.ch"],"email":"accounts-payable@acme.ch","id":"client_123","name":"ACME GmbH","notes":"Main contact for software projects"}},"privateClient":{"summary":"Private client","value":{"buildingNumber":"5","city":"Bern","country":"CH","email":"max@example.com","name":"Max Muster","street":"Musterweg","zip":"3007"}}},"schema":{"type":"object"}}},"required":true},"responses":{"201":{"content":{"application/json":{"examples":{"created":{"summary":"Created","value":{"data":{"buildingNumber":"12","ccEmails":["finance@acme.ch"],"city":"Z\u00fcrich","country":"CH","email":"billing@acme.ch","files":[],"id":"client_123","name":"ACME GmbH","notes":"","street":"Bahnhofstrasse","zip":"8001"},"success":true}}}}}},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}}}},"description":"Bad Request"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}}}},"description":"Unauthorized"},"409":{"content":{"application/json":{"examples":{"alreadyExists":{"summary":"Document ID already exists","value":{"error":"Document 'abc123' already exists","success":false}}}}},"description":"Conflict"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"}},"summary":"Create client","tags":["Clients"]}},"/clients/import":{"post":{"description":"Imports many clients at once from an uploaded spreadsheet (CSV or Excel). The pipeline auto-detects the file's structure (any language, any delimiter, any leading preamble), then uses Claude to map each row onto the client data model (name, email, address fields, etc.). Rows with no usable name and rows the model flags as non-client are skipped, and duplicates are skipped by matching name or email against the user's existing clients (and earlier rows in the same file). Surviving clients are written in batches. No fields outside the client model are ever created.","requestBody":{"content":{"multipart/form-data":{"examples":{"spreadsheetImport":{"summary":"Bulk import clients from a spreadsheet","value":{"file":"(binary: .csv or .xlsx/.xls spreadsheet of clients)","locale":"en"}}},"schema":{"properties":{"file":{"description":"CSV or Excel (.xlsx/.xls) spreadsheet of clients.","format":"binary","type":"string"},"locale":{"description":"Locale hint for AI text cleanup (e.g. `en`, `de`, `fr`). Defaults to the user's profile locale.","type":"string"}},"required":["file"],"type":"object"}}},"required":true},"responses":{"201":{"content":{"application/json":{"examples":{"importSummary":{"summary":"Successful import","value":{"ai_failures":0,"dropped_invalid":1,"dry_run":false,"errors":[],"imported":18,"skipped_by_ai":2,"skipped_duplicate":4,"structure_source":"ai","success":true,"total_rows":25}}}}},"description":"Spreadsheet processed; returns a summary of how rows were classified and how many were imported."},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}}}},"description":"Invalid multipart request, empty file, or unparseable spreadsheet"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}}}},"description":"Unauthorized"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"},"502":{"description":"Firestore batch write failed"}},"summary":"Bulk-import clients from a CSV/Excel spreadsheet","tags":["Clients"]}},"/clients/save":{"post":{"description":"Creates or updates a client. If `client.id` is provided, that client is updated; otherwise the API may match an existing client by name/email before creating a new one.","requestBody":{"content":{"application/json":{"examples":{"businessClient":{"summary":"Business client","value":{"client":{"buildingNumber":"12","ccEmails":["finance@acme.ch"],"city":"Z\u00fcrich","country":"CH","email":"billing@acme.ch","name":"ACME GmbH","street":"Bahnhofstrasse","zip":"8001"}}},"flatClientPayload":{"summary":"Flat payload","value":{"buildingNumber":"12","city":"Z\u00fcrich","country":"CH","email":"billing@acme.ch","name":"ACME GmbH","street":"Bahnhofstrasse","zip":"8001"}},"privateClient":{"summary":"Private person client","value":{"client":{"buildingNumber":"5","city":"Bern","country":"CH","email":"max@example.com","name":"Max Muster","street":"Musterweg","zip":"3007"}}},"updateExistingClient":{"summary":"Update by id","value":{"client":{"ccEmails":["finance@acme.ch","cfo@acme.ch"],"email":"accounts-payable@acme.ch","id":"client_123","name":"ACME GmbH","notes":"Main contact for software projects"}}}},"schema":{"properties":{"client":{"type":"object"}},"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"examples":{"savedBusinessClient":{"summary":"Saved business client","value":{"data":{"buildingNumber":"12","ccEmails":["finance@acme.ch"],"city":"Z\u00fcrich","country":"CH","email":"billing@acme.ch","files":[],"id":"client_123","name":"ACME GmbH","notes":"","street":"Bahnhofstrasse","zip":"8001"},"success":true}},"savedPrivateClient":{"summary":"Saved private client","value":{"data":{"buildingNumber":"5","ccEmails":[],"city":"Bern","country":"CH","email":"max@example.com","files":[],"id":"client_private_1","name":"Max Muster","notes":"","street":"Musterweg","zip":"3007"},"success":true}},"updatedClient":{"summary":"Updated existing client","value":{"data":{"buildingNumber":"12","ccEmails":["finance@acme.ch"],"city":"Z\u00fcrich","country":"CH","email":"accounts-payable@acme.ch","files":[],"id":"client_123","name":"ACME GmbH","notes":"","street":"Bahnhofstrasse","zip":"8001"},"success":true}}},"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}},"description":"Saved"},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}}}},"description":"Bad Request"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}},"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}},"description":"Invalid API key"},"409":{"content":{"application/json":{"examples":{"alreadyExists":{"summary":"Document ID already exists","value":{"error":"Document 'abc123' already exists","success":false}}}}},"description":"Conflict"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"}},"summary":"Save a client (create or update)","tags":["Clients"]}},"/clients/{id}":{"delete":{"responses":{"200":{"content":{"application/json":{"examples":{"deleted":{"summary":"Deleted","value":{"data":{"deleted":true,"id":"client_123"},"success":true}}}}}},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}}}},"description":"Bad Request"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"examples":{"notFound":{"summary":"Document not found","value":{"error":"Document not found","success":false}}}}},"description":"Not Found"},"409":{"content":{"application/json":{"examples":{"hasInvoices":{"summary":"Client still has invoices","value":{"details":{"invoiceIds":["inv_123","inv_124"]},"error":"Cannot delete a client that still has invoices. Delete the client's invoices first.","success":false}}}}},"description":"Conflict"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"}},"summary":"Delete client","tags":["Clients"]},"get":{"responses":{"200":{"content":{"application/json":{"examples":{"found":{"summary":"Document found","value":{"data":{"buildingNumber":"12","ccEmails":["finance@acme.ch"],"city":"Z\u00fcrich","country":"CH","email":"billing@acme.ch","files":[],"id":"client_123","name":"ACME GmbH","notes":"","street":"Bahnhofstrasse","zip":"8001"},"success":true}}}}}},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}}}},"description":"Bad Request"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"examples":{"notFound":{"summary":"Document not found","value":{"error":"Document not found","success":false}}}}},"description":"Not Found"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"}},"summary":"Get client","tags":["Clients"]},"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"patch":{"requestBody":{"content":{"application/json":{"examples":{"updateAddress":{"summary":"Update address","value":{"buildingNumber":"4","city":"Z\u00fcrich","street":"Neue Gasse","zip":"8002"}},"updateCc":{"summary":"Update CC emails","value":{"ccEmails":["finance@acme.ch","bookkeeper@acme.ch"]}},"updateEmail":{"summary":"Update email","value":{"email":"billing-new@acme.ch"}},"updateNotes":{"summary":"Update notes","value":{"notes":"Preferred billing contact is finance team"}}},"schema":{"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"examples":{"updated":{"summary":"Updated","value":{"data":{"buildingNumber":"12","ccEmails":["finance@acme.ch"],"city":"Z\u00fcrich","country":"CH","email":"billing@acme.ch","files":[],"id":"client_123","name":"ACME GmbH","notes":"","street":"Bahnhofstrasse","zip":"8001"},"success":true}}}}}},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}}}},"description":"Bad Request"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"examples":{"notFound":{"summary":"Document not found","value":{"error":"Document not found","success":false}}}}},"description":"Not Found"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"}},"summary":"Update client","tags":["Clients"]}},"/companies/autocomplete":{"get":{"description":"Type-ahead search over the Swiss commercial register (Zefix). Matching is accent-insensitive and token-prefix based, so `bellev rest` matches `Restaurant Bellevue`. Returns up to `limit` companies with their registered address (`street` = street name only, `buildingNumber`, `zip`, `city`). The street/number split is best-effort: when the registered address has no recognizable trailing house number (e.g. PO boxes or c/o lines), the full line is returned in `street` and `buildingNumber` is empty. `uid` is the company's UID (enterprise identification number, `CHE-XXX.XXX.XXX`). Zefix also supplies `legalForm` (for example `AG / SA` or `GmbH / S\u00e0rl`) and the stable eCH-0097 `legalFormCode`. It does not know whether the company is VAT-registered \u2014 to prefill a VAT number, pass the `uid` to `GET /companies/vat-number` after the user picks a company. Each `name` is pre-formatted to the invoice constraint (max 70 characters per line, at most 2 lines); `name_lines` gives the same value already split into those lines. Queries shorter than 2 characters return an empty list.","parameters":[{"description":"Search text (company or client name). Minimum 2 characters.","example":"viandar","in":"query","name":"q","required":true,"schema":{"minLength":2,"type":"string"}},{"description":"Maximum number of results (1-20, default 10).","in":"query","name":"limit","required":false,"schema":{"default":10,"maximum":20,"minimum":1,"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"examples":{"autocomplete":{"summary":"Auto-complete results","value":{"data":{"count":1,"items":[{"buildingNumber":"12","city":"Zermatt","id":"1755463","legalForm":"GmbH / S\u00e0rl","legalFormCode":"0107","name":"Viandar GmbH","name_lines":["Viandar GmbH"],"street":"Dorfstrasse","uid":"CHE-241.529.293","zip":"3920"}],"query":"viandar"},"success":true}}}}},"description":"Matching companies"},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}}}},"description":"Bad Request"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}},"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}},"description":"Invalid API key"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"},"503":{"description":"Company search temporarily unavailable"}},"summary":"Auto-complete Swiss companies (Zefix)","tags":["Companies"]}},"/companies/vat-number":{"get":{"description":"Checks the official BFS UID register (uid.admin.ch) to see whether the given UID is registered for Swiss VAT. Returns `vatRegistered` plus `vatNumber` (`CHE-XXX.XXX.XXX MWST`) when registered, or `vatNumber: null` when not. Call this with the `uid` from `/companies/autocomplete` after the user selects a company, and only prefill the VAT field when `vatRegistered` is true. Results are cached server-side; the upstream register is queried live otherwise.","parameters":[{"description":"Swiss UID, e.g. `CHE-242.294.601` (separators optional).","example":"CHE-242.294.601","in":"query","name":"uid","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"examples":{"notRegistered":{"summary":"Not VAT-registered","value":{"data":{"uid":"CHE-116.229.879","vatNumber":null,"vatRegistered":false},"success":true}},"registered":{"summary":"VAT-registered company","value":{"data":{"uid":"CHE-242.294.601","vatNumber":"CHE-242.294.601 MWST","vatRegistered":true},"success":true}}}}},"description":"VAT registration status"},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}},"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}},"description":"Malformed UID"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}},"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}},"description":"Invalid API key"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"},"503":{"description":"VAT register check temporarily unavailable"}},"summary":"Check VAT registration for a UID","tags":["Companies"]}},"/expense-categories":{"get":{"responses":{"200":{"content":{"application/json":{"examples":{"expenseCategories":{"summary":"Expense categories list","value":{"data":{"count":1,"items":[{"emoji":"\ud83d\ude86","id":"cat_travel","name":"Travel","type":"expense"}]},"success":true}},"multipleExpenseCategories":{"summary":"Multiple categories","value":{"data":{"count":2,"items":[{"emoji":"\ud83d\ude86","id":"cat_travel","name":"Travel","type":"expense"},{"emoji":"\ud83d\udcbb","id":"cat_software","name":"Software","type":"expense"}]},"success":true}}}}},"description":"OK"},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}}}},"description":"Bad Request"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}}}},"description":"Unauthorized"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"}},"summary":"List expense categories","tags":["Expense Categories"]},"post":{"requestBody":{"content":{"application/json":{"examples":{"meals":{"summary":"Meals","value":{"emoji":"\ud83c\udf7d\ufe0f","name":"Meals"}},"office":{"summary":"Office","value":{"emoji":"\ud83d\udcce","name":"Office"}},"software":{"summary":"Software","value":{"emoji":"\ud83d\udcbb","name":"Software"}},"travel":{"summary":"Travel","value":{"emoji":"\ud83d\ude86","name":"Travel"}}},"schema":{"type":"object"}}},"required":true},"responses":{"201":{"content":{"application/json":{"examples":{"createdExpenseCategory":{"summary":"Expense category created","value":{"data":{"emoji":"\ud83d\ude86","id":"cat_travel","name":"Travel","type":"expense"},"success":true}},"createdSoftwareCategory":{"summary":"Software category created","value":{"data":{"emoji":"\ud83d\udcbb","id":"cat_software","name":"Software","type":"expense"},"success":true}}}}},"description":"Created"},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}}}},"description":"Bad Request"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}}}},"description":"Unauthorized"},"409":{"content":{"application/json":{"examples":{"alreadyExists":{"summary":"Document ID already exists","value":{"error":"Document 'abc123' already exists","success":false}}}}},"description":"Conflict"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"}},"summary":"Create expense category","tags":["Expense Categories"]}},"/expense-categories/{id}":{"delete":{"responses":{"200":{"content":{"application/json":{"examples":{"deleted":{"summary":"Deleted expense category","value":{"data":{"deleted":true,"id":"cat_travel"},"success":true}}}}},"description":"OK"},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}}}},"description":"Bad Request"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"examples":{"notFound":{"summary":"Document not found","value":{"error":"Document not found","success":false}}}}},"description":"Not Found"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"}},"summary":"Delete expense category","tags":["Expense Categories"]},"get":{"responses":{"200":{"content":{"application/json":{"examples":{"expenseCategory":{"summary":"Expense category","value":{"data":{"emoji":"\ud83d\ude86","id":"cat_travel","name":"Travel","type":"expense"},"success":true}}}}},"description":"OK"},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}}}},"description":"Bad Request"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"examples":{"notFound":{"summary":"Document not found","value":{"error":"Document not found","success":false}}}}},"description":"Not Found"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"}},"summary":"Get expense category","tags":["Expense Categories"]},"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"patch":{"requestBody":{"content":{"application/json":{"examples":{"emojiTravel":{"summary":"Update emoji","value":{"emoji":"\ud83d\ude97"}},"renameMeals":{"summary":"Rename meals","value":{"name":"Meals & Entertainment"}},"renameOffice":{"summary":"Rename office","value":{"name":"Office Supplies"}},"renameTravel":{"summary":"Rename travel category","value":{"name":"Travel & Transport"}}},"schema":{"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"examples":{"expenseCategory":{"summary":"Expense category","value":{"data":{"emoji":"\ud83d\ude86","id":"cat_travel","name":"Travel","type":"expense"},"success":true}}}}},"description":"OK"},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}}}},"description":"Bad Request"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"examples":{"notFound":{"summary":"Document not found","value":{"error":"Document not found","success":false}}}}},"description":"Not Found"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"}},"summary":"Update expense category","tags":["Expense Categories"]}},"/expenses":{"get":{"description":"Returns the user's expenses, sorted by `date` descending.\n\n**Optional filters (combinable):**\n- `status=paid` or `status=unpaid` \u2014 filter by payment status\n- `year=YYYY` \u2014 only expenses whose `date` falls in the given calendar year (UTC)\n\nAny other query parameter returns a 400. Filter on additional fields (e.g. `categoryID`) client-side.","parameters":[{"description":"Filter by payment status.","example":"unpaid","in":"query","name":"status","required":false,"schema":{"enum":["paid","unpaid"],"type":"string"}},{"description":"Filter expenses whose `date` falls in the given calendar year (UTC).","example":2025,"in":"query","name":"year","required":false,"schema":{"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"examples":{"empty":{"summary":"No results","value":{"data":{"count":0,"items":[]},"success":true}},"listMany":{"summary":"Multiple results","value":{"data":{"count":2,"items":[{"amount":82.4,"categoryID":"cat_travel","date":"2026-02-20T08:00:00Z","description":"SBB ticket client meeting","documents":[],"id":"exp_123","isPaid":true,"originalAmount":null,"originalCurrency":null,"paymentMethodID":"pm_card","vatRate":8.1,"withholdingTax":false},{"amount":82.4,"categoryID":"cat_travel","date":"2026-02-20T08:00:00Z","description":"SBB ticket client meeting","documents":[],"id":"exp_124","isPaid":false,"originalAmount":null,"originalCurrency":null,"paymentMethodID":"pm_card","vatRate":8.1,"withholdingTax":false}]},"success":true}},"listOne":{"summary":"One result","value":{"data":{"count":1,"items":[{"amount":82.4,"categoryID":"cat_travel","date":"2026-02-20T08:00:00Z","description":"SBB ticket client meeting","documents":[],"id":"exp_123","isPaid":true,"originalAmount":null,"originalCurrency":null,"paymentMethodID":"pm_card","vatRate":8.1,"withholdingTax":false}]},"success":true}}}}}},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}}}},"description":"Bad Request"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}}}},"description":"Unauthorized"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"}},"summary":"List expenses","tags":["Expenses"]},"post":{"requestBody":{"content":{"application/json":{"examples":{"cashExpense":{"summary":"Cash office expense","value":{"amount":23.8,"category":"Office","date":"2026-02-18T12:00:00Z","description":"Office supplies","isPaid":true,"paymentMethod":"Cash","vatRate":8.1}},"foreignExpense":{"summary":"Foreign currency expense","value":{"amount":92.0,"category":"Software","description":"SaaS subscription","isPaid":true,"originalAmount":99.0,"originalCurrency":"USD","paymentMethod":"Credit Card","vatRate":0}},"travelExpense":{"summary":"Travel expense","value":{"amount":82.4,"category":"Travel","date":"2026-02-20T08:00:00Z","description":"SBB ticket client meeting","isPaid":true,"paymentMethod":"Credit Card","vatRate":8.1}},"unpaidExpense":{"summary":"Unpaid expense","value":{"amount":540,"category":"Professional Services","description":"Accounting services invoice","isPaid":false,"paymentMethod":"Bank transfer","vatRate":8.1}}},"schema":{"type":"object"}}},"required":true},"responses":{"201":{"content":{"application/json":{"examples":{"created":{"summary":"Created","value":{"data":{"amount":82.4,"categoryID":"cat_travel","date":"2026-02-20T08:00:00Z","description":"SBB ticket client meeting","documents":[],"id":"exp_123","isPaid":true,"originalAmount":null,"originalCurrency":null,"paymentMethodID":"pm_card","vatRate":8.1,"withholdingTax":false},"success":true}}}}}},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}}}},"description":"Bad Request"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}}}},"description":"Unauthorized"},"409":{"content":{"application/json":{"examples":{"alreadyExists":{"summary":"Document ID already exists","value":{"error":"Document 'abc123' already exists","success":false}}}}},"description":"Conflict"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"}},"summary":"Create expense","tags":["Expenses"]}},"/expenses/create":{"post":{"description":"Creates an expense record. Category and payment method can be specified by name (auto-created if they don't exist yet) or by ID.","requestBody":{"content":{"application/json":{"examples":{"cashExpense":{"summary":"Cash expense","value":{"expense":{"amount":23.8,"category":"Office","date":"2026-02-18T12:00:00Z","description":"Office supplies","isPaid":true,"paymentMethod":"Cash","vatRate":8.1}}},"foreignCurrencyExpense":{"summary":"Expense with original foreign currency","value":{"expense":{"amount":92.0,"category":"Software","description":"SaaS subscription","isPaid":true,"originalAmount":99.0,"originalCurrency":"USD","paymentMethod":"Credit Card","vatRate":0}}},"travelExpense":{"summary":"Travel expense","value":{"expense":{"amount":82.4,"category":"Travel","date":"2026-02-20T08:00:00Z","description":"SBB ticket client meeting","isPaid":true,"paymentMethod":"Credit Card","vatRate":8.1}}},"unpaidExpense":{"summary":"Unpaid expense","value":{"expense":{"amount":540,"category":"Professional Services","description":"Accounting services invoice","isPaid":false,"paymentMethod":"Bank transfer","vatRate":8.1}}}},"schema":{"type":"object"}}},"required":true},"responses":{"201":{"content":{"application/json":{"examples":{"expenseCreated":{"summary":"Expense created","value":{"data":{"expense":{"amount":82.4,"categoryID":"cat_travel","date":"2026-02-20T08:00:00Z","description":"SBB ticket client meeting","documents":[],"id":"exp_123","isPaid":true,"originalAmount":null,"originalCurrency":null,"paymentMethodID":"pm_card","vatRate":8.1,"withholdingTax":false}},"success":true}},"foreignExpenseCreated":{"summary":"Foreign currency expense","value":{"data":{"expense":{"amount":82.4,"categoryID":"cat_travel","date":"2026-02-20T08:00:00Z","description":"SBB ticket client meeting","documents":[],"id":"exp_124","isPaid":true,"originalAmount":99.0,"originalCurrency":"USD","paymentMethodID":"pm_card","vatRate":8.1,"withholdingTax":false}},"success":true}}}}},"description":"Expense created"},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}}}},"description":"Bad Request"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}}}},"description":"Unauthorized"},"409":{"content":{"application/json":{"examples":{"alreadyExists":{"summary":"Document ID already exists","value":{"error":"Document 'abc123' already exists","success":false}}}}},"description":"Conflict"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"}},"summary":"Create an expense","tags":["Expenses"]}},"/expenses/import":{"post":{"description":"Imports many expenses at once from an uploaded spreadsheet (CSV or Excel) or ISO-20022 CAMT bank statement (camt.052/.053/.054 XML). For spreadsheets the pipeline auto-detects the file's structure (any language, any delimiter, any leading preamble) and drops rows missing the minimum fields (date, amount, description); for CAMT files only booked outgoing debits become expenses \u2014 incoming credits and reversals are reported in `skipped_credits`, and each transaction gets a stable fingerprint so re-importing the same statement never creates duplicates. All rows are deduplicated against the user's existing expenses (date + amount within a 1-day tolerance), enriched via Claude (category and payment method are matched against the user's existing entries \u2014 never invented), re-checked for duplicates after AI normalisation, and finally written in batches. Foreign-currency amounts are converted to CHF and the originals are preserved in `originalAmount`/`originalCurrency`.","requestBody":{"content":{"multipart/form-data":{"examples":{"spreadsheetImport":{"summary":"Bulk import expenses from a spreadsheet","value":{"file":"(binary: .csv or .xlsx/.xls spreadsheet of expenses)","locale":"en"}}},"schema":{"properties":{"file":{"description":"CSV or Excel (.xlsx/.xls) spreadsheet of expenses, or an ISO-20022 CAMT XML bank statement.","format":"binary","type":"string"},"locale":{"description":"Locale hint for AI enrichment (e.g. `en`, `de`, `fr`). Defaults to the user's profile locale.","type":"string"}},"required":["file"],"type":"object"}}},"required":true},"responses":{"201":{"content":{"application/json":{"examples":{"importSummary":{"summary":"Successful import","value":{"ai_failures":0,"dropped_invalid":0,"dry_run":false,"errors":[],"imported":12,"skipped_by_ai":7,"skipped_credits":0,"skipped_duplicate":0,"skipped_post_ai_duplicate":0,"skipped_pre_ai_duplicate":0,"structure_source":"heuristic","success":true,"total_rows":19}}}}},"description":"Spreadsheet processed; returns a summary of how rows were classified and how many were imported."},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}}}},"description":"Invalid multipart request, empty file, or unparseable spreadsheet"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}}}},"description":"Unauthorized"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"},"502":{"description":"Firestore batch write failed"}},"summary":"Bulk-import expenses from a CSV/Excel spreadsheet or CAMT bank statement","tags":["Expenses"]}},"/expenses/scan-and-create":{"post":{"description":"Scans the uploaded receipt or invoice file, extracts expense fields, and creates an expense record for the authenticated user. The scan language is inferred from the user's account settings locale (fallback: `en`), and existing expense categories and payment methods from the user's account are used as suggestions during extraction.","requestBody":{"content":{"multipart/form-data":{"examples":{"receiptPdf":{"summary":"Receipt PDF scan + create","value":{"expense":"(binary file: receipt PDF/image/html)"}}},"schema":{"properties":{"expense":{"description":"Receipt/invoice file (PDF, image, or supported text/html input).","format":"binary","type":"string"}},"required":["expense"],"type":"object"}}},"required":true},"responses":{"201":{"content":{"application/json":{"examples":{"foreignCurrencyScannedAndCreated":{"summary":"Foreign currency scan converted to CHF expense","value":{"data":{"expense":{"amount":92.0,"categoryID":"cat_travel","date":"2026-02-20T08:00:00Z","description":"SBB ticket client meeting","documents":[],"id":"exp_124","isPaid":true,"originalAmount":99.0,"originalCurrency":"USD","paymentMethodID":"pm_card","vatRate":0,"withholdingTax":false},"scan":{"amount":99.0,"amount_original":99.0,"category":"Travel","currency":"USD","date":"2026-02-20","description":"SBB ticket client meeting","paid":true,"payment_method":"Credit Card","vat_amount":0.0,"vat_rate":0.0,"withholding_tax_relevant":false},"scanLocale":"en"},"success":true}},"scannedAndCreated":{"summary":"Receipt scanned and expense created","value":{"data":{"expense":{"amount":82.4,"categoryID":"cat_travel","date":"2026-02-20T08:00:00Z","description":"SBB ticket client meeting","documents":[],"id":"exp_123","isPaid":true,"originalAmount":null,"originalCurrency":null,"paymentMethodID":"pm_card","vatRate":8.1,"withholdingTax":false},"scan":{"amount":82.4,"category":"Travel","currency":"CHF","date":"2026-02-20","description":"SBB ticket client meeting","paid":true,"payment_method":"Credit Card","vat_amount":6.2,"vat_rate":8.1,"withholding_tax_relevant":false},"scanLocale":"en"},"success":true}}}}},"description":"Expense scanned and created"},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}}}},"description":"Invalid multipart request or missing file"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}}}},"description":"Unauthorized"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"},"502":{"content":{"application/json":{"examples":{"scanFailed":{"summary":"Scan provider failed","value":{"error":"Expense scan failed","success":false}}}}},"description":"Expense scan failed"},"503":{"content":{"application/json":{"examples":{"scanUnavailable":{"summary":"Scan unavailable","value":{"error":"Expense scan is temporarily unavailable.","success":false}}}}},"description":"Expense scan temporarily unavailable"}},"summary":"Scan an expense receipt and create the expense","tags":["Expenses"]}},"/expenses/{id}":{"delete":{"responses":{"200":{"content":{"application/json":{"examples":{"deleted":{"summary":"Deleted","value":{"data":{"deleted":true,"id":"exp_123"},"success":true}}}}}},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}}}},"description":"Bad Request"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"examples":{"notFound":{"summary":"Document not found","value":{"error":"Document not found","success":false}}}}},"description":"Not Found"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"}},"summary":"Delete expense","tags":["Expenses"]},"get":{"responses":{"200":{"content":{"application/json":{"examples":{"found":{"summary":"Document found","value":{"data":{"amount":82.4,"categoryID":"cat_travel","date":"2026-02-20T08:00:00Z","description":"SBB ticket client meeting","documents":[],"id":"exp_123","isPaid":true,"originalAmount":null,"originalCurrency":null,"paymentMethodID":"pm_card","vatRate":8.1,"withholdingTax":false},"success":true}}}}}},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}}}},"description":"Bad Request"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"examples":{"notFound":{"summary":"Document not found","value":{"error":"Document not found","success":false}}}}},"description":"Not Found"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"}},"summary":"Get expense","tags":["Expenses"]},"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"patch":{"requestBody":{"content":{"application/json":{"examples":{"foreignAmounts":{"summary":"Update foreign currency values","value":{"originalAmount":99,"originalCurrency":"USD"}},"markPaid":{"summary":"Mark paid","value":{"isPaid":true}},"setCategoryRefs":{"summary":"Set category and payment method IDs","value":{"categoryID":"cat_travel","paymentMethodID":"pm_card"}},"updateAmount":{"summary":"Update amount/VAT","value":{"amount":89.4,"vatRate":8.1}}},"schema":{"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"examples":{"updated":{"summary":"Updated","value":{"data":{"amount":82.4,"categoryID":"cat_travel","date":"2026-02-20T08:00:00Z","description":"SBB ticket client meeting","documents":[],"id":"exp_123","isPaid":true,"originalAmount":null,"originalCurrency":null,"paymentMethodID":"pm_card","vatRate":8.1,"withholdingTax":false},"success":true}}}}}},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}}}},"description":"Bad Request"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"examples":{"notFound":{"summary":"Document not found","value":{"error":"Document not found","success":false}}}}},"description":"Not Found"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"}},"summary":"Update expense","tags":["Expenses"]}},"/health":{"get":{"responses":{"200":{"content":{"application/json":{"examples":{"ok":{"summary":"Healthy","value":{"data":{"status":"ok"},"success":true}}}}},"description":"OK"}},"security":[],"summary":"Health check","tags":["Public"]}},"/invoice-items":{"get":{"description":"Returns all reusable invoice item templates (products, articles, services), sorted by `name`.\n\nNo query parameters. Filter the response client-side if needed.","responses":{"200":{"content":{"application/json":{"examples":{"empty":{"summary":"No results","value":{"data":{"count":0,"items":[]},"success":true}},"listMany":{"summary":"Multiple results","value":{"data":{"count":2,"items":[{"details":null,"discount":0,"id":"tmpl_consulting","isCreditNoteItem":false,"name":"Consulting hour","price":180,"units":1,"usageCount":2,"vatRate":8.1},{"details":null,"discount":0,"id":"tmpl_hourly","isCreditNoteItem":false,"name":"On-site consulting day","price":180,"units":1,"usageCount":2,"vatRate":8.1}]},"success":true}},"listOne":{"summary":"One result","value":{"data":{"count":1,"items":[{"details":null,"discount":0,"id":"tmpl_consulting","isCreditNoteItem":false,"name":"Consulting hour","price":180,"units":1,"usageCount":2,"vatRate":8.1}]},"success":true}}}}}},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}}}},"description":"Bad Request"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}}}},"description":"Unauthorized"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"}},"summary":"List invoice items","tags":["Invoice Items (Products/Articles)"]},"post":{"requestBody":{"content":{"application/json":{"examples":{"consultingHour":{"summary":"Consulting hour template","value":{"name":"Consulting hour","price":180,"units":1,"usageCount":2,"vatRate":8.1}},"discountLine":{"summary":"Discount line","value":{"name":"Discount","price":-50,"units":1,"vatRate":0}},"hourlyDay":{"summary":"Hourly details template","value":{"details":{"date":"2026-02-20T00:00:00Z","from":"09:00","to":"17:00","type":"HourlyRateDetails"},"name":"On-site consulting day","price":1440,"units":1,"vatRate":8.1}},"travelFlat":{"summary":"Travel flat fee","value":{"name":"Travel flat fee","price":120,"units":1,"vatRate":8.1}}},"schema":{"type":"object"}}},"required":true},"responses":{"201":{"content":{"application/json":{"examples":{"created":{"summary":"Created","value":{"data":{"details":null,"discount":0,"id":"tmpl_consulting","isCreditNoteItem":false,"name":"Consulting hour","price":180,"units":1,"usageCount":2,"vatRate":8.1},"success":true}}}}}},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}}}},"description":"Bad Request"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}}}},"description":"Unauthorized"},"409":{"content":{"application/json":{"examples":{"alreadyExists":{"summary":"Document ID already exists","value":{"error":"Document 'abc123' already exists","success":false}}}}},"description":"Conflict"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"}},"summary":"Create invoice item","tags":["Invoice Items (Products/Articles)"]}},"/invoice-items/import":{"post":{"description":"Imports many articles/products at once from an uploaded spreadsheet (CSV or Excel). The pipeline auto-detects the file's structure, then uses Claude to map each row onto the invoice-item model (name, price, units, vatRate). Rows with no usable name and rows the model flags as non-article are skipped, and duplicates are skipped by matching the normalised name against the user's existing items (and earlier rows in the same file). Surviving items are written in batches.","requestBody":{"content":{"multipart/form-data":{"examples":{"spreadsheetImport":{"summary":"Bulk import articles from a spreadsheet","value":{"file":"(binary: .csv or .xlsx/.xls spreadsheet of articles/products)","locale":"en"}}},"schema":{"properties":{"file":{"description":"CSV or Excel (.xlsx/.xls) spreadsheet of articles/products.","format":"binary","type":"string"},"locale":{"description":"Locale hint for AI text cleanup (e.g. `en`, `de`, `fr`). Defaults to the user's profile locale.","type":"string"}},"required":["file"],"type":"object"}}},"required":true},"responses":{"201":{"content":{"application/json":{"examples":{"importSummary":{"summary":"Successful import","value":{"ai_failures":0,"dropped_invalid":1,"dry_run":false,"errors":[],"imported":18,"skipped_by_ai":2,"skipped_duplicate":4,"structure_source":"ai","success":true,"total_rows":25}}}}},"description":"Spreadsheet processed; returns a summary of how rows were classified and how many were imported."},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}}}},"description":"Invalid multipart request, empty file, or unparseable spreadsheet"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}}}},"description":"Unauthorized"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"},"502":{"description":"Firestore batch write failed"}},"summary":"Bulk-import articles (invoice items) from a CSV/Excel spreadsheet","tags":["Invoice Items (Products/Articles)"]}},"/invoice-items/{id}":{"delete":{"responses":{"200":{"content":{"application/json":{"examples":{"deleted":{"summary":"Deleted","value":{"data":{"deleted":true,"id":"tmpl_consulting"},"success":true}}}}}},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}}}},"description":"Bad Request"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"examples":{"notFound":{"summary":"Document not found","value":{"error":"Document not found","success":false}}}}},"description":"Not Found"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"}},"summary":"Delete invoice item","tags":["Invoice Items (Products/Articles)"]},"get":{"responses":{"200":{"content":{"application/json":{"examples":{"found":{"summary":"Document found","value":{"data":{"details":null,"discount":0,"id":"tmpl_consulting","isCreditNoteItem":false,"name":"Consulting hour","price":180,"units":1,"usageCount":2,"vatRate":8.1},"success":true}}}}}},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}}}},"description":"Bad Request"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"examples":{"notFound":{"summary":"Document not found","value":{"error":"Document not found","success":false}}}}},"description":"Not Found"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"}},"summary":"Get invoice item","tags":["Invoice Items (Products/Articles)"]},"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"patch":{"requestBody":{"content":{"application/json":{"examples":{"renameTemplate":{"summary":"Rename template","value":{"name":"Senior consulting hour"}},"updateDetails":{"summary":"Set hourly details","value":{"details":{"date":"2026-02-20T00:00:00Z","from":"09:00","to":"17:00","type":"HourlyRateDetails"}}},"updatePrice":{"summary":"Update price","value":{"price":195}},"updateVat":{"summary":"Update VAT rate","value":{"vatRate":8.1}}},"schema":{"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"examples":{"updated":{"summary":"Updated","value":{"data":{"details":null,"discount":0,"id":"tmpl_consulting","isCreditNoteItem":false,"name":"Consulting hour","price":180,"units":1,"usageCount":2,"vatRate":8.1},"success":true}}}}}},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}}}},"description":"Bad Request"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"examples":{"notFound":{"summary":"Document not found","value":{"error":"Document not found","success":false}}}}},"description":"Not Found"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"}},"summary":"Update invoice item","tags":["Invoice Items (Products/Articles)"]}},"/invoices":{"get":{"description":"Returns the user's invoices and quotes, sorted by `date` descending.\n\n**Optional filters (combinable):**\n- `status=paid` or `status=unpaid` \u2014 filter by payment status\n- `type=invoice` or `type=quote` \u2014 filter by document type\n- `year=YYYY` \u2014 only invoices whose `date` falls in the given calendar year (UTC)\n\nAny other query parameter returns a 400. Filter on additional fields client-side.","parameters":[{"description":"Filter by payment status.","example":"unpaid","in":"query","name":"status","required":false,"schema":{"enum":["paid","unpaid"],"type":"string"}},{"description":"Filter by document type. `invoice` matches stored `type=normal`.","example":"invoice","in":"query","name":"type","required":false,"schema":{"enum":["invoice","quote"],"type":"string"}},{"description":"Filter invoices whose `date` falls in the given calendar year (UTC).","example":2025,"in":"query","name":"year","required":false,"schema":{"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"examples":{"empty":{"summary":"No results","value":{"data":{"count":0,"items":[]},"success":true}},"listMany":{"summary":"Multiple results","value":{"data":{"count":2,"items":[{"bankId":"bank_chf","clientRef":"users/<uid>/clients/client_123","currency":"CHF","date":"2026-02-25T00:00:00Z","description":"Vielen Dank f\u00fcr Ihren Auftrag.","disableRounding":true,"exchangeRate":null,"generationCount":0,"headerDescription":"","hideQuotePricing":false,"id":"inv_123","isArchived":false,"isPaid":false,"items":[{"details":null,"discount":0,"id":"line_1","isCreditNoteItem":false,"name":"Software development","price":180,"units":8,"usageCount":0,"vatRate":8.1},{"details":null,"discount":0,"id":"line_2","isCreditNoteItem":false,"name":"Project management","price":160,"units":2,"usageCount":0,"vatRate":8.1}],"language":"de","payments":{"items":[],"proof":null},"recurring":"disabled","recurringAutoSend":false,"recurringDueDate":null,"sent":false,"sentEmailHistory":[],"type":"normal","vatEnabled":true,"vatMode":"additive","version":11},{"bankId":"bank_chf","clientRef":"users/<uid>/clients/client_123","currency":"EUR","date":"2026-02-25T00:00:00Z","description":"Quote valid for 30 days","disableRounding":true,"exchangeRate":null,"generationCount":0,"headerDescription":"","hideQuotePricing":false,"id":"quote_456","isArchived":false,"isPaid":false,"items":[{"details":null,"discount":0,"id":"line_q1","isCreditNoteItem":false,"name":"Discovery workshop","price":900,"units":1,"usageCount":0,"vatRate":0}],"language":"en","payments":{"items":[],"proof":null},"recurring":"disabled","recurringAutoSend":false,"recurringDueDate":null,"sent":false,"sentEmailHistory":[],"type":"quote","vatEnabled":false,"vatMode":"additive","version":11}]},"success":true}},"listOne":{"summary":"One result","value":{"data":{"count":1,"items":[{"bankId":"bank_chf","clientRef":"users/<uid>/clients/client_123","currency":"CHF","date":"2026-02-25T00:00:00Z","description":"Vielen Dank f\u00fcr Ihren Auftrag.","disableRounding":true,"exchangeRate":null,"generationCount":0,"headerDescription":"","hideQuotePricing":false,"id":"inv_123","isArchived":false,"isPaid":false,"items":[{"details":null,"discount":0,"id":"line_1","isCreditNoteItem":false,"name":"Software development","price":180,"units":8,"usageCount":0,"vatRate":8.1},{"details":null,"discount":0,"id":"line_2","isCreditNoteItem":false,"name":"Project management","price":160,"units":2,"usageCount":0,"vatRate":8.1}],"language":"de","payments":{"items":[],"proof":null},"recurring":"disabled","recurringAutoSend":false,"recurringDueDate":null,"sent":false,"sentEmailHistory":[],"type":"normal","vatEnabled":true,"vatMode":"additive","version":11}]},"success":true}}}}}},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}}}},"description":"Bad Request"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}}}},"description":"Unauthorized"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"}},"summary":"List invoices","tags":["Invoices"]},"post":{"requestBody":{"content":{"application/json":{"examples":{"hourlyInvoice":{"summary":"Hourly invoice","value":{"clientId":"client_123","currency":"CHF","headerDescription":"Consulting services","items":[{"details":{"date":"2026-02-25T00:00:00Z","from":"09:00","to":"17:00","type":"HourlyRateDetails"},"name":"Consulting","price":1200,"units":1,"vatRate":8.1}],"language":"en","vatEnabled":true,"vatMode":"inclusive"}},"invoiceVat":{"summary":"Invoice with VAT","value":{"bankId":"bank_chf","clientId":"client_123","currency":"CHF","description":"Vielen Dank f\u00fcr Ihren Auftrag.","items":[{"name":"Software development","price":180,"units":8,"vatRate":8.1},{"name":"Project management","price":160,"units":2,"vatRate":8.1}],"language":"de","vatEnabled":true,"vatMode":"additive"}},"quoteNoVat":{"summary":"Quote without VAT","value":{"clientId":"client_123","currency":"EUR","description":"Quote valid for 30 days","items":[{"name":"Discovery workshop","price":900,"units":1,"vatRate":0}],"language":"en","type":"quote","vatEnabled":false}},"recurringInvoice":{"summary":"Recurring invoice","value":{"clientId":"client_123","currency":"CHF","description":"Monatliches Hosting und Wartung","items":[{"name":"Hosting","price":95,"units":1,"vatRate":8.1},{"name":"Maintenance","price":180,"units":1,"vatRate":8.1}],"language":"de","recurring":"monthly","type":"normal","vatEnabled":true,"vatMode":"additive"}}},"schema":{"type":"object"}}},"required":true},"responses":{"201":{"content":{"application/json":{"examples":{"created":{"summary":"Created","value":{"data":{"bankId":"bank_chf","clientRef":"users/<uid>/clients/client_123","currency":"CHF","date":"2026-02-25T00:00:00Z","description":"Vielen Dank f\u00fcr Ihren Auftrag.","disableRounding":true,"exchangeRate":null,"generationCount":0,"headerDescription":"","hideQuotePricing":false,"id":"inv_123","isArchived":false,"isPaid":false,"items":[{"details":null,"discount":0,"id":"line_1","isCreditNoteItem":false,"name":"Software development","price":180,"units":8,"usageCount":0,"vatRate":8.1},{"details":null,"discount":0,"id":"line_2","isCreditNoteItem":false,"name":"Project management","price":160,"units":2,"usageCount":0,"vatRate":8.1}],"language":"de","payments":{"items":[],"proof":null},"recurring":"disabled","recurringAutoSend":false,"recurringDueDate":null,"sent":false,"sentEmailHistory":[],"type":"normal","vatEnabled":true,"vatMode":"additive","version":11},"success":true}}}}}},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}}}},"description":"Bad Request"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}}}},"description":"Unauthorized"},"409":{"content":{"application/json":{"examples":{"alreadyExists":{"summary":"Document ID already exists","value":{"error":"Document 'abc123' already exists","success":false}}}}},"description":"Conflict"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"}},"summary":"Create invoice","tags":["Invoices"]}},"/invoices/create":{"post":{"description":"Creates an invoice draft with sensible defaults and generated line item IDs. You can include `client` details inline.","requestBody":{"content":{"application/json":{"examples":{"hourlyDetails":{"summary":"Invoice with hourly item details","value":{"clientId":"client_123","invoice":{"currency":"CHF","headerDescription":"Consulting services","items":[{"details":{"date":"2026-02-25T00:00:00Z","from":"09:00","to":"17:00","type":"HourlyRateDetails"},"name":"Consulting","price":1200,"units":1,"vatRate":8.1}],"language":"en","vatEnabled":true,"vatMode":"inclusive"}}},"invoiceWithVat":{"summary":"Invoice with VAT","value":{"client":{"buildingNumber":"12","ccEmails":["finance@acme.ch"],"city":"Z\u00fcrich","country":"CH","email":"billing@acme.ch","name":"ACME GmbH","street":"Bahnhofstrasse","zip":"8001"},"invoice":{"bankId":"bank_chf","currency":"CHF","description":"Vielen Dank f\u00fcr Ihren Auftrag.","items":[{"name":"Software development","price":180,"units":8,"vatRate":8.1},{"name":"Project management","price":160,"units":2,"vatRate":8.1}],"language":"de","vatEnabled":true,"vatMode":"additive"}}},"quoteNoVat":{"summary":"Quote without VAT","value":{"clientId":"client_123","invoice":{"currency":"EUR","description":"Quote valid for 30 days","items":[{"name":"Discovery workshop","price":900,"units":1,"vatRate":0}],"language":"en","type":"quote","vatEnabled":false}}},"recurringDraft":{"summary":"Recurring monthly draft","value":{"clientId":"client_123","invoice":{"currency":"CHF","description":"Monatliches Hosting und Wartung","items":[{"name":"Hosting","price":95,"units":1,"vatRate":8.1},{"name":"Maintenance","price":180,"units":1,"vatRate":8.1}],"language":"de","recurring":"monthly","type":"normal","vatEnabled":true,"vatMode":"additive"}}}},"schema":{"properties":{"client":{"type":"object"},"clientId":{"type":"string"},"invoice":{"type":"object"}},"type":"object"}}},"required":true},"responses":{"201":{"content":{"application/json":{"examples":{"invoiceDraftCreated":{"summary":"Invoice draft created","value":{"data":{"clientId":"client_123","invoice":{"bankId":"bank_chf","clientRef":"users/<uid>/clients/client_123","currency":"CHF","date":"2026-02-25T00:00:00Z","description":"Vielen Dank f\u00fcr Ihren Auftrag.","disableRounding":true,"exchangeRate":null,"generationCount":0,"headerDescription":"","hideQuotePricing":false,"id":"inv_123","isArchived":false,"isPaid":false,"items":[{"details":null,"discount":0,"id":"line_1","isCreditNoteItem":false,"name":"Software development","price":180,"units":8,"usageCount":0,"vatRate":8.1},{"details":null,"discount":0,"id":"line_2","isCreditNoteItem":false,"name":"Project management","price":160,"units":2,"usageCount":0,"vatRate":8.1}],"language":"de","payments":{"items":[],"proof":null},"recurring":"disabled","recurringAutoSend":false,"recurringDueDate":null,"sent":false,"sentEmailHistory":[],"type":"normal","vatEnabled":true,"vatMode":"additive","version":11}},"success":true}},"invoiceWithInlineClient":{"summary":"Invoice + client created","value":{"data":{"clientId":"client_123","invoice":{"bankId":"bank_chf","clientRef":"users/<uid>/clients/client_123","currency":"CHF","date":"2026-02-25T00:00:00Z","description":"Vielen Dank f\u00fcr Ihren Auftrag.","disableRounding":true,"exchangeRate":null,"generationCount":0,"headerDescription":"","hideQuotePricing":false,"id":"inv_123","isArchived":false,"isPaid":false,"items":[{"details":null,"discount":0,"id":"line_1","isCreditNoteItem":false,"name":"Software development","price":180,"units":8,"usageCount":0,"vatRate":8.1},{"details":null,"discount":0,"id":"line_2","isCreditNoteItem":false,"name":"Project management","price":160,"units":2,"usageCount":0,"vatRate":8.1}],"language":"de","payments":{"items":[],"proof":null},"recurring":"disabled","recurringAutoSend":false,"recurringDueDate":null,"sent":false,"sentEmailHistory":[],"type":"normal","vatEnabled":true,"vatMode":"additive","version":11}},"success":true}},"quoteDraftCreated":{"summary":"Quote draft created","value":{"data":{"clientId":"client_123","invoice":{"bankId":"bank_chf","clientRef":"users/<uid>/clients/client_123","currency":"EUR","date":"2026-02-25T00:00:00Z","description":"Quote valid for 30 days","disableRounding":true,"exchangeRate":null,"generationCount":0,"headerDescription":"","hideQuotePricing":false,"id":"quote_456","isArchived":false,"isPaid":false,"items":[{"details":null,"discount":0,"id":"line_q1","isCreditNoteItem":false,"name":"Discovery workshop","price":900,"units":1,"usageCount":0,"vatRate":0}],"language":"en","payments":{"items":[],"proof":null},"recurring":"disabled","recurringAutoSend":false,"recurringDueDate":null,"sent":false,"sentEmailHistory":[],"type":"quote","vatEnabled":false,"vatMode":"additive","version":11}},"success":true}}},"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}},"description":"Invoice created"},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}}}},"description":"Bad Request"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}}}},"description":"Unauthorized"},"409":{"content":{"application/json":{"examples":{"alreadyExists":{"summary":"Document ID already exists","value":{"error":"Document 'abc123' already exists","success":false}}}}},"description":"Invoice ID already exists"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"}},"summary":"Create an invoice draft","tags":["Invoices"]}},"/invoices/create-and-generate":{"post":{"description":"Creates a new invoice draft and immediately generates the invoice PDF in one call.","requestBody":{"content":{"application/json":{"examples":{"hourlyConsulting":{"summary":"Create + generate hourly invoice","value":{"clientId":"client_123","generate":{"testMode":false,"version":"v1l"},"invoice":{"currency":"CHF","headerDescription":"Consulting services","items":[{"details":{"date":"2026-02-25T00:00:00Z","from":"09:00","to":"17:00","type":"HourlyRateDetails"},"name":"Consulting","price":1200,"units":1,"vatRate":8.1}],"language":"en","vatEnabled":true,"vatMode":"inclusive"}}},"quotePdf":{"summary":"Create + generate quote","value":{"clientId":"client_123","generate":{"overrides":{"invoice":{"type":"quote"}},"testMode":false,"version":"v1l"},"invoice":{"currency":"EUR","description":"Quote valid for 30 days","items":[{"name":"Discovery workshop","price":900,"units":1,"vatRate":0}],"language":"en","type":"quote","vatEnabled":false}}},"recurringTestPdf":{"summary":"Create + generate in test mode","value":{"clientId":"client_123","generate":{"locale":"en","testMode":true,"version":"v1l"},"invoice":{"currency":"CHF","description":"Monatliches Hosting und Wartung","items":[{"name":"Hosting","price":95,"units":1,"vatRate":8.1},{"name":"Maintenance","price":180,"units":1,"vatRate":8.1}],"language":"de","recurring":"monthly","type":"normal","vatEnabled":true,"vatMode":"additive"}}},"standardInvoice":{"summary":"Create + generate invoice","value":{"client":{"buildingNumber":"12","ccEmails":["finance@acme.ch"],"city":"Z\u00fcrich","country":"CH","email":"billing@acme.ch","name":"ACME GmbH","street":"Bahnhofstrasse","zip":"8001"},"generate":{"testMode":false,"version":"v1l"},"invoice":{"bankId":"bank_chf","currency":"CHF","description":"Vielen Dank f\u00fcr Ihren Auftrag.","items":[{"name":"Software development","price":180,"units":8,"vatRate":8.1},{"name":"Project management","price":160,"units":2,"vatRate":8.1}],"language":"de","vatEnabled":true,"vatMode":"additive"}}}},"schema":{"type":"object"}}},"required":true},"responses":{"201":{"content":{"application/json":{"examples":{"createdAndGenerated":{"summary":"Invoice created and PDF generated","value":{"data":{"clientId":"client_123","generate":{"function":"create_invoice_v1l","raw":{"result":{"filename":"Invoice_2026_0011","url":"https://storage.googleapis.com/magic-heidi.appspot.com/.../Invoice_2026_0011.pdf"}},"result":{"filename":"Invoice_2026_0011","url":"https://storage.googleapis.com/magic-heidi.appspot.com/.../Invoice_2026_0011.pdf"},"status":200},"invoice":{"bankId":"bank_chf","clientRef":"users/<uid>/clients/client_123","currency":"CHF","date":"2026-02-25T00:00:00Z","description":"Vielen Dank f\u00fcr Ihren Auftrag.","disableRounding":true,"exchangeRate":null,"generationCount":0,"headerDescription":"","hideQuotePricing":false,"id":"inv_123","isArchived":false,"isPaid":false,"items":[{"details":null,"discount":0,"id":"line_1","isCreditNoteItem":false,"name":"Software development","price":180,"units":8,"usageCount":0,"vatRate":8.1},{"details":null,"discount":0,"id":"line_2","isCreditNoteItem":false,"name":"Project management","price":160,"units":2,"usageCount":0,"vatRate":8.1}],"language":"de","payments":{"items":[],"proof":null},"recurring":"disabled","recurringAutoSend":false,"recurringDueDate":null,"sent":false,"sentEmailHistory":[],"type":"normal","vatEnabled":true,"vatMode":"additive","version":11}},"success":true}},"quoteGenerated":{"summary":"Quote created and PDF generated","value":{"data":{"clientId":"client_123","generate":{"function":"create_invoice_v1l","raw":{"result":{"filename":"Invoice_2026_0011","url":"https://storage.googleapis.com/magic-heidi.appspot.com/.../Invoice_2026_0011.pdf"}},"result":{"filename":"Invoice_2026_0011","url":"https://storage.googleapis.com/magic-heidi.appspot.com/.../Invoice_2026_0011.pdf"},"status":200},"invoice":{"bankId":"bank_chf","clientRef":"users/<uid>/clients/client_123","currency":"EUR","date":"2026-02-25T00:00:00Z","description":"Quote valid for 30 days","disableRounding":true,"exchangeRate":null,"generationCount":0,"headerDescription":"","hideQuotePricing":false,"id":"quote_456","isArchived":false,"isPaid":false,"items":[{"details":null,"discount":0,"id":"line_q1","isCreditNoteItem":false,"name":"Discovery workshop","price":900,"units":1,"usageCount":0,"vatRate":0}],"language":"en","payments":{"items":[],"proof":null},"recurring":"disabled","recurringAutoSend":false,"recurringDueDate":null,"sent":false,"sentEmailHistory":[],"type":"quote","vatEnabled":false,"vatMode":"additive","version":11}},"success":true}}}}},"description":"Created and generated"},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}}}},"description":"Bad Request"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}}}},"description":"Unauthorized"},"429":{"content":{"application/json":{"examples":{"rateLimited":{"summary":"Rate limited","value":{"details":{"response":{"error":{"message":"Too many requests! Please try again later","status":"RESOURCE_EXHAUSTED"}},"status":429},"error":"Too many requests! Please try again later","success":false}}}}},"description":"Too Many Requests"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"},"503":{"content":{"application/json":{"examples":{"temporarilyUnavailable":{"summary":"Service temporarily unavailable","value":{"error":"This action is temporarily unavailable.","success":false}}}}},"description":"Service Unavailable"}},"summary":"Create invoice draft and generate PDF","tags":["Invoices"]}},"/invoices/create-generate-and-send":{"post":{"description":"One-call workflow to create an invoice draft, generate the PDF, and send the invoice email.","requestBody":{"content":{"application/json":{"examples":{"invoiceEmail":{"summary":"Create + generate + send invoice","value":{"client":{"buildingNumber":"12","ccEmails":["finance@acme.ch"],"city":"Z\u00fcrich","country":"CH","email":"billing@acme.ch","name":"ACME GmbH","street":"Bahnhofstrasse","zip":"8001"},"generate":{"version":"v1l"},"invoice":{"bankId":"bank_chf","currency":"CHF","description":"Vielen Dank f\u00fcr Ihren Auftrag.","items":[{"name":"Software development","price":180,"units":8,"vatRate":8.1},{"name":"Project management","price":160,"units":2,"vatRate":8.1}],"language":"de","vatEnabled":true,"vatMode":"additive"},"send":{"ccEmails":["finance@acme.ch"],"toEmail":"billing@acme.ch"}}},"quoteEmail":{"summary":"Create + generate + send quote","value":{"clientId":"client_123","generate":{"overrides":{"invoice":{"type":"quote"}},"testMode":false,"version":"v1l"},"invoice":{"currency":"EUR","description":"Quote valid for 30 days","items":[{"name":"Discovery workshop","price":900,"units":1,"vatRate":0}],"language":"en","type":"quote","vatEnabled":false},"send":{"ccEmails":[],"invoiceStatus":"quote","locale":"en","toEmail":"procurement@acme.ch"}}},"reminderEmail":{"summary":"Create + generate + send reminder-style email","value":{"clientId":"client_123","generate":{"locale":"de","overrides":{"invoice":{"paymentStatus":"late"}},"version":"v1l"},"invoice":{"bankId":"bank_chf","currency":"CHF","description":"Vielen Dank f\u00fcr Ihren Auftrag.","items":[{"name":"Software development","price":180,"units":8,"vatRate":8.1},{"name":"Project management","price":160,"units":2,"vatRate":8.1}],"language":"de","vatEnabled":true,"vatMode":"additive"},"send":{"ccEmails":["finance@acme.ch"],"invoiceStatus":"reminder","locale":"de","overrides":{"invoice":{"paymentStatus":"late"}},"toEmail":"billing@acme.ch"}}},"testModeEmail":{"summary":"Create + generate + send in test mode","value":{"client":{"buildingNumber":"5","city":"Bern","country":"CH","email":"max@example.com","name":"Max Muster","street":"Musterweg","zip":"3007"},"generate":{"locale":"en","testMode":true,"version":"v1l"},"invoice":{"currency":"CHF","headerDescription":"Consulting services","items":[{"details":{"date":"2026-02-25T00:00:00Z","from":"09:00","to":"17:00","type":"HourlyRateDetails"},"name":"Consulting","price":1200,"units":1,"vatRate":8.1}],"language":"en","vatEnabled":true,"vatMode":"inclusive"},"send":{"locale":"en","testMode":true,"toEmail":"billing@acme.ch","userEmail":"owner@example.com"}}}},"schema":{"type":"object"}}},"required":true},"responses":{"201":{"content":{"application/json":{"examples":{"createdGeneratedSent":{"summary":"Invoice created, generated, sent","value":{"data":{"clientId":"client_123","generate":{"function":"create_invoice_v1l","raw":{"result":{"filename":"Invoice_2026_0011","url":"https://storage.googleapis.com/magic-heidi.appspot.com/.../Invoice_2026_0011.pdf"}},"result":{"filename":"Invoice_2026_0011","url":"https://storage.googleapis.com/magic-heidi.appspot.com/.../Invoice_2026_0011.pdf"},"status":200},"invoice":{"bankId":"bank_chf","clientRef":"users/<uid>/clients/client_123","currency":"CHF","date":"2026-02-25T00:00:00Z","description":"Vielen Dank f\u00fcr Ihren Auftrag.","disableRounding":true,"exchangeRate":null,"generationCount":1,"headerDescription":"","hideQuotePricing":false,"id":"inv_123","isArchived":false,"isPaid":true,"items":[{"details":null,"discount":0,"id":"line_1","isCreditNoteItem":false,"name":"Software development","price":180,"units":8,"usageCount":0,"vatRate":8.1},{"details":null,"discount":0,"id":"line_2","isCreditNoteItem":false,"name":"Project management","price":160,"units":2,"usageCount":0,"vatRate":8.1}],"language":"de","payments":{"items":[{"amount":1760.0,"date":"2026-02-25T10:30:00Z","id":"pay_1","methodId":"pm_bank"}],"proof":null},"recurring":"disabled","recurringAutoSend":false,"recurringDueDate":null,"sent":true,"sentEmailHistory":[{"ccEmails":["finance@acme.ch"],"date":"2026-02-25T10:40:00Z","email":"billing@acme.ch","invoiceStatus":"invoice","isTest":false,"success":true}],"type":"normal","vatEnabled":true,"vatMode":"additive","version":11},"send":{"function":"send_invoice_v1a","raw":{"result":true},"result":true,"status":200}},"success":true}},"quoteGeneratedSent":{"summary":"Quote created, generated, sent","value":{"data":{"clientId":"client_123","generate":{"function":"create_invoice_v1l","raw":{"result":{"filename":"Invoice_2026_0011","url":"https://storage.googleapis.com/magic-heidi.appspot.com/.../Invoice_2026_0011.pdf"}},"result":{"filename":"Invoice_2026_0011","url":"https://storage.googleapis.com/magic-heidi.appspot.com/.../Invoice_2026_0011.pdf"},"status":200},"invoice":{"bankId":"bank_chf","clientRef":"users/<uid>/clients/client_123","currency":"EUR","date":"2026-02-25T00:00:00Z","description":"Quote valid for 30 days","disableRounding":true,"exchangeRate":null,"generationCount":0,"headerDescription":"","hideQuotePricing":false,"id":"quote_456","isArchived":false,"isPaid":false,"items":[{"details":null,"discount":0,"id":"line_q1","isCreditNoteItem":false,"name":"Discovery workshop","price":900,"units":1,"usageCount":0,"vatRate":0}],"language":"en","payments":{"items":[],"proof":null},"recurring":"disabled","recurringAutoSend":false,"recurringDueDate":null,"sent":false,"sentEmailHistory":[],"type":"quote","vatEnabled":false,"vatMode":"additive","version":11},"send":{"function":"send_invoice_v1a","raw":{"result":true},"result":true,"status":200}},"success":true}}}}},"description":"Created, generated, and sent"},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}}}},"description":"Bad Request"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}}}},"description":"Unauthorized"},"429":{"content":{"application/json":{"examples":{"rateLimited":{"summary":"Rate limited","value":{"details":{"response":{"error":{"message":"Too many requests! Please try again later","status":"RESOURCE_EXHAUSTED"}},"status":429},"error":"Too many requests! Please try again later","success":false}}}}},"description":"Too Many Requests"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"},"503":{"content":{"application/json":{"examples":{"temporarilyUnavailable":{"summary":"Service temporarily unavailable","value":{"error":"This action is temporarily unavailable.","success":false}}}}},"description":"Service Unavailable"}},"summary":"Create invoice, generate PDF, and send email","tags":["Invoices"]}},"/invoices/from-credit":{"post":{"description":"The manual override for `/invoices/import`. Every credit the import left uncreated \u2014 `unclassified` (the model was unsure) and `nonInvoiceable` (it judged the credit not to be revenue) \u2014 comes back with a ready-made `payload` under `details`; post that object here verbatim to create the invoice anyway, optionally overriding `clientName` and `itemName` first. The invoice is built by the importer's own builder: stored paid, payment recorded on the transaction date, VAT inclusive, and carrying the same `importRef`, so a later re-import of the statement recognises it. Confirming the same credit twice returns the first invoice (`alreadyExisted: true`) instead of creating a second.","requestBody":{"content":{"application/json":{"example":{"clientName":"Google","credit":{"amount":1669.74,"counterparty":"GOOGLE PAYMENT CORP","currency":"CHF","date":"2026-06-16","importRef":"camt:8f2a1c\u2026"},"itemName":"Google payout 16.06.2026"},"schema":{"properties":{"clientName":{"description":"Who to invoice. Defaults to the credit's payer, then its description.","type":"string"},"credit":{"properties":{"amount":{"type":"number"},"counterparty":{"type":"string"},"currency":{"default":"CHF","type":"string"},"date":{"format":"date","type":"string"},"description":{"type":"string"},"importRef":{"description":"Fingerprint from the import summary; makes the confirmation idempotent.","type":"string"}},"required":["date","amount"],"type":"object"},"itemName":{"description":"What was sold. Defaults to the client name.","type":"string"}},"required":["credit"],"type":"object"}}},"required":true},"responses":{"200":{"description":"This credit was already confirmed; the existing invoice is returned"},"201":{"description":"Invoice created"},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}}}},"description":"The credit has no usable date, amount or client name"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}}}},"description":"Unauthorized"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"}},"summary":"Create the paid invoice for one bank credit the importer did not book","tags":["Invoices"]}},"/invoices/import":{"post":{"description":"The revenue counterpart of `/expenses/import`: takes the same kind of file (CSV/Excel or ISO-20022 CAMT XML) and turns *incoming credits* into invoices. Credits are first matched against the user's unpaid invoices \u2014 a match records the payment and marks that invoice paid instead of creating a new one. Only the leftovers can become invoices, and they must survive: an exact re-import fingerprint check (`importRef`), a deterministic filter that rejects cash deposits, card refunds and transfers between the user's own accounts, two amount/date/payer duplicate checks against existing payments and earlier auto-created invoices, and finally a conservative Claude gate that must find positive evidence of a customer paying for goods or services. Payouts from card acquirers (SumUp, TWINT, Adyen\u2026) and from app stores, marketplaces and payout platforms (Google Payment Corp, Apple, Shopify, Amazon, Stripe, PayPal, Etsy, Uber, Booking.com\u2026) become one invoice per payout \u2014 that money is the business's own sales, settled in batches. Anything the model is unsure about is returned under `unclassified` for review and is never created. Rows under `unclassified` and `nonInvoiceable` carry a `payload` in `details` that can be posted to `/invoices/from-credit` to create the invoice anyway. Created invoices are stored paid, with the received amount recorded as a payment on the transaction date and VAT stored inclusive so the invoice total equals the money received.","requestBody":{"content":{"multipart/form-data":{"schema":{"properties":{"dryRun":{"description":"Set to `true` to run the full pipeline (parse, match, classify, AI) and report what would happen without writing anything.","type":"string"},"file":{"description":"CSV/Excel bank statement, or an ISO-20022 CAMT XML statement.","format":"binary","type":"string"},"locale":{"description":"Locale hint for AI text (e.g. `en`, `de`, `fr`). Defaults to the user's profile locale.","type":"string"},"markPaid":{"description":"Set to `false` to record payments on matched invoices without flipping `isPaid`. Defaults to true.","type":"string"}},"required":["file"],"type":"object"}}},"required":true},"responses":{"201":{"content":{"application/json":{"examples":{"importSummary":{"summary":"Successful import","value":{"ai_failures":0,"created":[{"amount":39.4,"clientCreated":true,"clientId":"cli_1","clientName":"Sumup","currency":"CHF","date":"2026-01-30","importRef":"camt:8f2a1c\u2026","invoiceId":"abc123","itemName":"Versement Sumup du 30.01.2026","kind":"acquirer_payout","version":41}],"dry_run":false,"errors":[],"imported":91,"matched":{"applied":[],"markedPaid":2,"partialPayments":0,"suggestions":[]},"nonInvoiceable":[{"amount":220.0,"counterparty":"","date":"2026-01-07","description":"D\u00e9p\u00f4t bancomat CHF 220.00","reason":"card_transaction"}],"skippedDuplicates":[],"skipped_duplicate":0,"success":true,"total_rows":123,"unclassified":[{"aiReason":"identifiable payer but no reference to a service","amount":70.0,"counterparty":"Oscar Jon Hamilton","date":"2026-07-24","description":"Cr\u00e9dit Instant Payment Oscar Jon Hamilton"}]}}}}},"description":"Statement processed; returns what was matched, created, skipped as duplicate, rejected, and left for review."},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}}}},"description":"Invalid multipart request, empty file, or unparseable statement"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}}}},"description":"Unauthorized"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"},"502":{"description":"Firestore batch write failed"}},"summary":"Bulk-create paid invoices from the incoming credits of a bank statement","tags":["Invoices"]}},"/invoices/scan-statement":{"post":{"description":"Parses one or more uploaded bank-statement files (CSV or Excel) into transactions, keeps incoming credits, and matches them against the user's unpaid (non-quote) invoices. Matching is reference-first: an invoice reference (`hash`, e.g. `NA001`) or an exact-total amount with a strong client-name match is auto-applied (invoice marked paid + a payment recorded); a credit below the total that strongly identifies an invoice records a partial payment without marking it paid. Lower-confidence amount-only matches are returned as `suggestions` and never applied. Re-importing the same statement is idempotent (payments are de-duplicated by amount + date). When a non-CHF invoice is marked paid, its CHF exchange rate is stored.","requestBody":{"content":{"multipart/form-data":{"schema":{"properties":{"files":{"description":"One or more CSV/Excel bank-statement files (repeat the field for multiple files).","format":"binary","type":"string"},"markPaid":{"description":"Set to `false` to record payments without flipping `isPaid` on full matches. Defaults to true.","type":"string"}},"required":["files"],"type":"object"}}},"required":true},"responses":{"200":{"description":"Statement processed; returns a summary plus the applied matches, suggestions and unmatched transactions."},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}}}},"description":"Missing file field"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}}}},"description":"Unauthorized"},"422":{"description":"No bank transactions could be parsed from the uploaded file(s)"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"}},"summary":"Match a bank statement against unpaid invoices","tags":["Invoices"]}},"/invoices/{id}":{"delete":{"responses":{"200":{"content":{"application/json":{"examples":{"deleted":{"summary":"Deleted","value":{"data":{"deleted":true,"id":"inv_123"},"success":true}}}}}},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}}}},"description":"Bad Request"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"examples":{"notFound":{"summary":"Document not found","value":{"error":"Document not found","success":false}}}}},"description":"Not Found"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"}},"summary":"Delete invoice","tags":["Invoices"]},"get":{"responses":{"200":{"content":{"application/json":{"examples":{"found":{"summary":"Document found","value":{"data":{"bankId":"bank_chf","clientRef":"users/<uid>/clients/client_123","currency":"CHF","date":"2026-02-25T00:00:00Z","description":"Vielen Dank f\u00fcr Ihren Auftrag.","disableRounding":true,"exchangeRate":null,"generationCount":0,"headerDescription":"","hideQuotePricing":false,"id":"inv_123","isArchived":false,"isPaid":false,"items":[{"details":null,"discount":0,"id":"line_1","isCreditNoteItem":false,"name":"Software development","price":180,"units":8,"usageCount":0,"vatRate":8.1},{"details":null,"discount":0,"id":"line_2","isCreditNoteItem":false,"name":"Project management","price":160,"units":2,"usageCount":0,"vatRate":8.1}],"language":"de","payments":{"items":[],"proof":null},"recurring":"disabled","recurringAutoSend":false,"recurringDueDate":null,"sent":false,"sentEmailHistory":[],"type":"normal","vatEnabled":true,"vatMode":"additive","version":11},"success":true}}}}}},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}}}},"description":"Bad Request"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"examples":{"notFound":{"summary":"Document not found","value":{"error":"Document not found","success":false}}}}},"description":"Not Found"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"}},"summary":"Get invoice","tags":["Invoices"]},"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"patch":{"requestBody":{"content":{"application/json":{"examples":{"changeDescription":{"summary":"Update descriptions","value":{"description":"Updated payment terms","headerDescription":"Project phase 2"}},"setRecurring":{"summary":"Set recurring monthly","value":{"recurring":"monthly"}},"toggleVat":{"summary":"Disable VAT","value":{"vatEnabled":false}},"updateItems":{"summary":"Replace line items","value":{"items":[{"name":"Software development","price":180,"units":8,"vatRate":8.1},{"name":"Project management","price":160,"units":2,"vatRate":8.1}]}}},"schema":{"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"examples":{"updated":{"summary":"Updated","value":{"data":{"bankId":"bank_chf","clientRef":"users/<uid>/clients/client_123","currency":"CHF","date":"2026-02-25T00:00:00Z","description":"Vielen Dank f\u00fcr Ihren Auftrag.","disableRounding":true,"exchangeRate":null,"generationCount":0,"headerDescription":"","hideQuotePricing":false,"id":"inv_123","isArchived":false,"isPaid":false,"items":[{"details":null,"discount":0,"id":"line_1","isCreditNoteItem":false,"name":"Software development","price":180,"units":8,"usageCount":0,"vatRate":8.1},{"details":null,"discount":0,"id":"line_2","isCreditNoteItem":false,"name":"Project management","price":160,"units":2,"usageCount":0,"vatRate":8.1}],"language":"de","payments":{"items":[],"proof":null},"recurring":"disabled","recurringAutoSend":false,"recurringDueDate":null,"sent":false,"sentEmailHistory":[],"type":"normal","vatEnabled":true,"vatMode":"additive","version":11},"success":true}}}}}},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}}}},"description":"Bad Request"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"examples":{"notFound":{"summary":"Document not found","value":{"error":"Document not found","success":false}}}}},"description":"Not Found"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"}},"summary":"Update invoice","tags":["Invoices"]}},"/invoices/{id}/generate":{"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"post":{"description":"Generates an invoice PDF and returns a file URL (typically in `result.url`).","requestBody":{"content":{"application/json":{"examples":{"default":{"summary":"Default generation","value":{"testMode":false,"version":"v1l"}},"quote":{"summary":"Generate as quote","value":{"overrides":{"invoice":{"type":"quote"}},"testMode":false,"version":"v1l"}},"reminder":{"summary":"Generate late payment reminder","value":{"locale":"de","overrides":{"invoice":{"paymentStatus":"late"}},"version":"v1l"}},"testMode":{"summary":"Generate in test mode","value":{"locale":"en","testMode":true,"version":"v1l"}}},"schema":{"properties":{"locale":{"type":"string"},"overrides":{"type":"object"},"testMode":{"default":false,"type":"boolean"},"version":{"default":"v1l","type":"string"}},"type":"object"}}},"required":false},"responses":{"200":{"content":{"application/json":{"examples":{"generated":{"summary":"PDF generated","value":{"data":{"function":"create_invoice_v1l","raw":{"result":{"filename":"Invoice_2026_0011","url":"https://storage.googleapis.com/magic-heidi.appspot.com/.../Invoice_2026_0011.pdf"}},"result":{"filename":"Invoice_2026_0011","url":"https://storage.googleapis.com/magic-heidi.appspot.com/.../Invoice_2026_0011.pdf"},"status":200},"success":true}},"generatedQuote":{"summary":"Quote PDF generated","value":{"data":{"function":"create_invoice_v1l","raw":{"result":{"filename":"Invoice_2026_0011","url":"https://storage.googleapis.com/magic-heidi.appspot.com/.../Invoice_2026_0011.pdf"}},"result":{"filename":"Quote_2026_0011","url":"https://storage.googleapis.com/.../Quote_2026_0011.pdf"},"status":200},"success":true}}}}},"description":"Generated"},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}}}},"description":"Bad Request"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}}}},"description":"Unauthorized"},"429":{"content":{"application/json":{"examples":{"rateLimited":{"summary":"Rate limited","value":{"details":{"response":{"error":{"message":"Too many requests! Please try again later","status":"RESOURCE_EXHAUSTED"}},"status":429},"error":"Too many requests! Please try again later","success":false}}}}},"description":"Too Many Requests"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"},"503":{"content":{"application/json":{"examples":{"temporarilyUnavailable":{"summary":"Service temporarily unavailable","value":{"error":"This action is temporarily unavailable.","success":false}}}}},"description":"Service Unavailable"}},"summary":"Generate invoice PDF","tags":["Invoices"]}},"/invoices/{id}/generate-and-send":{"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"post":{"description":"Convenience endpoint that first generates the PDF (unless `skipGenerate=true`) and then sends the invoice email.","requestBody":{"content":{"application/json":{"examples":{"quote":{"summary":"Generate and send quote","value":{"generate":{"overrides":{"invoice":{"type":"quote"}},"testMode":false,"version":"v1l"},"send":{"ccEmails":[],"invoiceStatus":"quote","locale":"en","toEmail":"procurement@acme.ch"}}},"reminder":{"summary":"Generate and send reminder","value":{"generate":{"locale":"de","overrides":{"invoice":{"paymentStatus":"late"}},"version":"v1l"},"send":{"ccEmails":["finance@acme.ch"],"invoiceStatus":"reminder","locale":"de","overrides":{"invoice":{"paymentStatus":"late"}},"toEmail":"billing@acme.ch"}}},"skipGenerate":{"summary":"Send only (skip generation)","value":{"send":{"ccEmails":[],"toEmail":"billing@acme.ch"},"skipGenerate":true}},"standard":{"summary":"Generate and send","value":{"generate":{"testMode":false,"version":"v1l"},"send":{"ccEmails":["finance@acme.ch"],"locale":"de","toEmail":"billing@acme.ch"}}}},"schema":{"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"examples":{"generatedAndSent":{"summary":"Generated and sent","value":{"data":{"generate":{"function":"create_invoice_v1l","raw":{"result":{"filename":"Invoice_2026_0011","url":"https://storage.googleapis.com/magic-heidi.appspot.com/.../Invoice_2026_0011.pdf"}},"result":{"filename":"Invoice_2026_0011","url":"https://storage.googleapis.com/magic-heidi.appspot.com/.../Invoice_2026_0011.pdf"},"status":200},"invoice":{"bankId":"bank_chf","clientRef":"users/<uid>/clients/client_123","currency":"CHF","date":"2026-02-25T00:00:00Z","description":"Vielen Dank f\u00fcr Ihren Auftrag.","disableRounding":true,"exchangeRate":null,"generationCount":1,"headerDescription":"","hideQuotePricing":false,"id":"inv_123","isArchived":false,"isPaid":true,"items":[{"details":null,"discount":0,"id":"line_1","isCreditNoteItem":false,"name":"Software development","price":180,"units":8,"usageCount":0,"vatRate":8.1},{"details":null,"discount":0,"id":"line_2","isCreditNoteItem":false,"name":"Project management","price":160,"units":2,"usageCount":0,"vatRate":8.1}],"language":"de","payments":{"items":[{"amount":1760.0,"date":"2026-02-25T10:30:00Z","id":"pay_1","methodId":"pm_bank"}],"proof":null},"recurring":"disabled","recurringAutoSend":false,"recurringDueDate":null,"sent":true,"sentEmailHistory":[{"ccEmails":["finance@acme.ch"],"date":"2026-02-25T10:40:00Z","email":"billing@acme.ch","invoiceStatus":"invoice","isTest":false,"success":true}],"type":"normal","vatEnabled":true,"vatMode":"additive","version":11},"send":{"function":"send_invoice_v1a","raw":{"result":true},"result":true,"status":200}},"success":true}},"sentOnly":{"summary":"Send only (skipGenerate)","value":{"data":{"generate":null,"invoice":{"bankId":"bank_chf","clientRef":"users/<uid>/clients/client_123","currency":"CHF","date":"2026-02-25T00:00:00Z","description":"Vielen Dank f\u00fcr Ihren Auftrag.","disableRounding":true,"exchangeRate":null,"generationCount":1,"headerDescription":"","hideQuotePricing":false,"id":"inv_123","isArchived":false,"isPaid":true,"items":[{"details":null,"discount":0,"id":"line_1","isCreditNoteItem":false,"name":"Software development","price":180,"units":8,"usageCount":0,"vatRate":8.1},{"details":null,"discount":0,"id":"line_2","isCreditNoteItem":false,"name":"Project management","price":160,"units":2,"usageCount":0,"vatRate":8.1}],"language":"de","payments":{"items":[{"amount":1760.0,"date":"2026-02-25T10:30:00Z","id":"pay_1","methodId":"pm_bank"}],"proof":null},"recurring":"disabled","recurringAutoSend":false,"recurringDueDate":null,"sent":true,"sentEmailHistory":[{"ccEmails":["finance@acme.ch"],"date":"2026-02-25T10:40:00Z","email":"billing@acme.ch","invoiceStatus":"invoice","isTest":false,"success":true}],"type":"normal","vatEnabled":true,"vatMode":"additive","version":11},"send":{"function":"send_invoice_v1a","raw":{"result":true},"result":true,"status":200}},"success":true}}}}},"description":"Generated and sent"},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}}}},"description":"Bad Request"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}}}},"description":"Unauthorized"},"429":{"content":{"application/json":{"examples":{"rateLimited":{"summary":"Rate limited","value":{"details":{"response":{"error":{"message":"Too many requests! Please try again later","status":"RESOURCE_EXHAUSTED"}},"status":429},"error":"Too many requests! Please try again later","success":false}}}}},"description":"Too Many Requests"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"},"503":{"content":{"application/json":{"examples":{"temporarilyUnavailable":{"summary":"Service temporarily unavailable","value":{"error":"This action is temporarily unavailable.","success":false}}}}},"description":"Service Unavailable"}},"summary":"Generate PDF and send invoice email in one call","tags":["Invoices"]}},"/invoices/{id}/mark-paid":{"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"post":{"description":"Marks the invoice as paid. Optionally also marks it as sent.","requestBody":{"content":{"application/json":{"examples":{"default":{"summary":"Mark paid","value":{}},"idempotent":{"summary":"Repeat mark paid","value":{"sent":true}},"markPaidAndSent":{"summary":"Mark paid and sent","value":{"sent":true}},"markPaidKeepUnsent":{"summary":"Mark paid, keep sent=false","value":{"sent":false}}},"schema":{"type":"object"}}},"required":false},"responses":{"200":{"content":{"application/json":{"examples":{"markedPaid":{"summary":"Invoice marked as paid","value":{"data":{"invoice":{"bankId":"bank_chf","clientRef":"users/<uid>/clients/client_123","currency":"CHF","date":"2026-02-25T00:00:00Z","description":"Vielen Dank f\u00fcr Ihren Auftrag.","disableRounding":true,"exchangeRate":null,"generationCount":0,"headerDescription":"","hideQuotePricing":false,"id":"inv_123","isArchived":false,"isPaid":true,"items":[{"details":null,"discount":0,"id":"line_1","isCreditNoteItem":false,"name":"Software development","price":180,"units":8,"usageCount":0,"vatRate":8.1},{"details":null,"discount":0,"id":"line_2","isCreditNoteItem":false,"name":"Project management","price":160,"units":2,"usageCount":0,"vatRate":8.1}],"language":"de","payments":{"items":[],"proof":null},"recurring":"disabled","recurringAutoSend":false,"recurringDueDate":null,"sent":false,"sentEmailHistory":[],"type":"normal","vatEnabled":true,"vatMode":"additive","version":11}},"success":true}},"markedPaidAndSent":{"summary":"Invoice marked as paid and sent","value":{"data":{"invoice":{"bankId":"bank_chf","clientRef":"users/<uid>/clients/client_123","currency":"CHF","date":"2026-02-25T00:00:00Z","description":"Vielen Dank f\u00fcr Ihren Auftrag.","disableRounding":true,"exchangeRate":null,"generationCount":0,"headerDescription":"","hideQuotePricing":false,"id":"inv_123","isArchived":false,"isPaid":true,"items":[{"details":null,"discount":0,"id":"line_1","isCreditNoteItem":false,"name":"Software development","price":180,"units":8,"usageCount":0,"vatRate":8.1},{"details":null,"discount":0,"id":"line_2","isCreditNoteItem":false,"name":"Project management","price":160,"units":2,"usageCount":0,"vatRate":8.1}],"language":"de","payments":{"items":[],"proof":null},"recurring":"disabled","recurringAutoSend":false,"recurringDueDate":null,"sent":true,"sentEmailHistory":[],"type":"normal","vatEnabled":true,"vatMode":"additive","version":11}},"success":true}}}}},"description":"OK"},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}}}},"description":"Bad Request"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}}}},"description":"Unauthorized"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"}},"summary":"Mark invoice as paid","tags":["Invoices"]}},"/invoices/{id}/mark-sent":{"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"post":{"description":"Sets the invoice's sent status to true without actually sending an email. Useful when the invoice was delivered outside of Magic Heidi.","requestBody":{"content":{"application/json":{"examples":{"alternative":{"summary":"Mark sent again","value":{}},"emptyObject":{"summary":"Mark sent (empty body)","value":{}},"minimal":{"summary":"Minimal body","value":{}},"repeat":{"summary":"Idempotent mark sent","value":{}}},"schema":{"type":"object"}}},"required":false},"responses":{"200":{"content":{"application/json":{"examples":{"markedSent":{"summary":"Invoice marked as sent","value":{"data":{"invoice":{"bankId":"bank_chf","clientRef":"users/<uid>/clients/client_123","currency":"CHF","date":"2026-02-25T00:00:00Z","description":"Vielen Dank f\u00fcr Ihren Auftrag.","disableRounding":true,"exchangeRate":null,"generationCount":0,"headerDescription":"","hideQuotePricing":false,"id":"inv_123","isArchived":false,"isPaid":false,"items":[{"details":null,"discount":0,"id":"line_1","isCreditNoteItem":false,"name":"Software development","price":180,"units":8,"usageCount":0,"vatRate":8.1},{"details":null,"discount":0,"id":"line_2","isCreditNoteItem":false,"name":"Project management","price":160,"units":2,"usageCount":0,"vatRate":8.1}],"language":"de","payments":{"items":[],"proof":null},"recurring":"disabled","recurringAutoSend":false,"recurringDueDate":null,"sent":true,"sentEmailHistory":[],"type":"normal","vatEnabled":true,"vatMode":"additive","version":11}},"success":true}}}}},"description":"OK"},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}}}},"description":"Bad Request"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}}}},"description":"Unauthorized"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"}},"summary":"Mark invoice as sent (without emailing)","tags":["Invoices"]}},"/invoices/{id}/mark-unpaid":{"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"post":{"description":"Reverts the invoice to unpaid status. Use `clearPayments: true` to also remove all recorded payment entries.","requestBody":{"content":{"application/json":{"examples":{"clearPayments":{"summary":"Mark unpaid and clear payments","value":{"clearPayments":true}},"default":{"summary":"Mark unpaid","value":{}},"keepPayments":{"summary":"Mark unpaid but keep payment history","value":{"clearPayments":false}},"minimal":{"summary":"Minimal payload","value":{}}},"schema":{"type":"object"}}},"required":false},"responses":{"200":{"content":{"application/json":{"examples":{"markedUnpaid":{"summary":"Invoice marked as unpaid","value":{"data":{"invoice":{"bankId":"bank_chf","clientRef":"users/<uid>/clients/client_123","currency":"CHF","date":"2026-02-25T00:00:00Z","description":"Vielen Dank f\u00fcr Ihren Auftrag.","disableRounding":true,"exchangeRate":null,"generationCount":1,"headerDescription":"","hideQuotePricing":false,"id":"inv_123","isArchived":false,"isPaid":false,"items":[{"details":null,"discount":0,"id":"line_1","isCreditNoteItem":false,"name":"Software development","price":180,"units":8,"usageCount":0,"vatRate":8.1},{"details":null,"discount":0,"id":"line_2","isCreditNoteItem":false,"name":"Project management","price":160,"units":2,"usageCount":0,"vatRate":8.1}],"language":"de","payments":{"items":[{"amount":1760.0,"date":"2026-02-25T10:30:00Z","id":"pay_1","methodId":"pm_bank"}],"proof":null},"recurring":"disabled","recurringAutoSend":false,"recurringDueDate":null,"sent":true,"sentEmailHistory":[{"ccEmails":["finance@acme.ch"],"date":"2026-02-25T10:40:00Z","email":"billing@acme.ch","invoiceStatus":"invoice","isTest":false,"success":true}],"type":"normal","vatEnabled":true,"vatMode":"additive","version":11}},"success":true}},"markedUnpaidClearedPayments":{"summary":"Invoice marked unpaid and payments cleared","value":{"data":{"invoice":{"bankId":"bank_chf","clientRef":"users/<uid>/clients/client_123","currency":"CHF","date":"2026-02-25T00:00:00Z","description":"Vielen Dank f\u00fcr Ihren Auftrag.","disableRounding":true,"exchangeRate":null,"generationCount":1,"headerDescription":"","hideQuotePricing":false,"id":"inv_123","isArchived":false,"isPaid":false,"items":[{"details":null,"discount":0,"id":"line_1","isCreditNoteItem":false,"name":"Software development","price":180,"units":8,"usageCount":0,"vatRate":8.1},{"details":null,"discount":0,"id":"line_2","isCreditNoteItem":false,"name":"Project management","price":160,"units":2,"usageCount":0,"vatRate":8.1}],"language":"de","payments":{"items":[],"proof":null},"recurring":"disabled","recurringAutoSend":false,"recurringDueDate":null,"sent":true,"sentEmailHistory":[{"ccEmails":["finance@acme.ch"],"date":"2026-02-25T10:40:00Z","email":"billing@acme.ch","invoiceStatus":"invoice","isTest":false,"success":true}],"type":"normal","vatEnabled":true,"vatMode":"additive","version":11}},"success":true}}}}},"description":"OK"},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}}}},"description":"Bad Request"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}}}},"description":"Unauthorized"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"}},"summary":"Mark invoice as unpaid","tags":["Invoices"]}},"/invoices/{id}/record-payment":{"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"post":{"description":"Records a payment entry on the invoice. Set `markPaid: true` to also mark the invoice as fully paid, or `false` to record a partial payment.","requestBody":{"content":{"application/json":{"examples":{"bankTransfer":{"summary":"Full payment by bank transfer","value":{"amount":1760.0,"date":"2026-02-25T10:30:00Z","markPaid":true,"methodName":"Bank transfer"}},"creditCard":{"summary":"Payment by credit card","value":{"amount":129.9,"markPaid":true,"methodName":"Credit Card"}},"existingMethodId":{"summary":"Use existing payment method ID","value":{"amount":2400,"date":"2026-02-28T14:45:00Z","markPaid":true,"methodId":"pm_bank_transfer"}},"partialPayment":{"summary":"Partial payment, keep invoice unpaid","value":{"amount":500.0,"date":"2026-02-10T09:00:00Z","markPaid":false,"methodName":"Bank transfer"}}},"schema":{"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"examples":{"fullPayment":{"summary":"Full payment recorded","value":{"data":{"invoice":{"bankId":"bank_chf","clientRef":"users/<uid>/clients/client_123","currency":"CHF","date":"2026-02-25T00:00:00Z","description":"Vielen Dank f\u00fcr Ihren Auftrag.","disableRounding":true,"exchangeRate":null,"generationCount":1,"headerDescription":"","hideQuotePricing":false,"id":"inv_123","isArchived":false,"isPaid":true,"items":[{"details":null,"discount":0,"id":"line_1","isCreditNoteItem":false,"name":"Software development","price":180,"units":8,"usageCount":0,"vatRate":8.1},{"details":null,"discount":0,"id":"line_2","isCreditNoteItem":false,"name":"Project management","price":160,"units":2,"usageCount":0,"vatRate":8.1}],"language":"de","payments":{"items":[{"amount":1760.0,"date":"2026-02-25T10:30:00Z","id":"pay_1","methodId":"pm_bank"}],"proof":null},"recurring":"disabled","recurringAutoSend":false,"recurringDueDate":null,"sent":true,"sentEmailHistory":[{"ccEmails":["finance@acme.ch"],"date":"2026-02-25T10:40:00Z","email":"billing@acme.ch","invoiceStatus":"invoice","isTest":false,"success":true}],"type":"normal","vatEnabled":true,"vatMode":"additive","version":11}},"success":true}},"partialPayment":{"summary":"Partial payment recorded","value":{"data":{"invoice":{"bankId":"bank_chf","clientRef":"users/<uid>/clients/client_123","currency":"CHF","date":"2026-02-25T00:00:00Z","description":"Vielen Dank f\u00fcr Ihren Auftrag.","disableRounding":true,"exchangeRate":null,"generationCount":0,"headerDescription":"","hideQuotePricing":false,"id":"inv_123","isArchived":false,"isPaid":false,"items":[{"details":null,"discount":0,"id":"line_1","isCreditNoteItem":false,"name":"Software development","price":180,"units":8,"usageCount":0,"vatRate":8.1},{"details":null,"discount":0,"id":"line_2","isCreditNoteItem":false,"name":"Project management","price":160,"units":2,"usageCount":0,"vatRate":8.1}],"language":"de","payments":{"items":[{"amount":500.0,"date":"2026-02-10T09:00:00Z","id":"pay_1","methodId":"pm_bank"}],"proof":null},"recurring":"disabled","recurringAutoSend":false,"recurringDueDate":null,"sent":false,"sentEmailHistory":[],"type":"normal","vatEnabled":true,"vatMode":"additive","version":11}},"success":true}}}}},"description":"Payment recorded"},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}}}},"description":"Bad Request"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}}}},"description":"Unauthorized"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"}},"summary":"Record a payment on an invoice","tags":["Invoices"]}},"/invoices/{id}/send":{"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"post":{"description":"Sends an invoice/quote/reminder email and records a send log in `sentEmailHistory`.\n\nUse `invoiceStatus` to control how the email is sent and logged:\n- `invoice` (default) \u2014 standard invoice email\n- `quote` or `offer` \u2014 quote/offer email\n- `reminder` \u2014 payment reminder email\n- `confirmation` \u2014 payment confirmation email\n\nAlternatively, use `overrides` to set the invoice type or payment status for the email template.","requestBody":{"content":{"application/json":{"examples":{"confirmationEmail":{"summary":"Send payment confirmation","value":{"ccEmails":[],"invoiceStatus":"confirmation","locale":"de","toEmail":"billing@acme.ch"}},"invoiceEmail":{"summary":"Send invoice","value":{"ccEmails":["finance@acme.ch"],"locale":"de","toEmail":"billing@acme.ch"}},"quoteEmail":{"summary":"Send quote (invoiceStatus)","value":{"ccEmails":[],"invoiceStatus":"quote","locale":"en","toEmail":"procurement@acme.ch"}},"reminderEmail":{"summary":"Send payment reminder","value":{"ccEmails":["finance@acme.ch"],"invoiceStatus":"reminder","locale":"de","overrides":{"invoice":{"paymentStatus":"late"}},"toEmail":"billing@acme.ch"}}},"schema":{"properties":{"ccEmails":{"items":{"type":"string"},"type":"array"},"invoiceStatus":{"description":"Type of email to send. Controls the email template and is recorded in sentEmailHistory.","enum":["invoice","quote","offer","reminder","confirmation"],"type":"string"},"locale":{"type":"string"},"overrides":{"type":"object"},"testMode":{"default":false,"type":"boolean"},"toEmail":{"type":"string"},"userEmail":{"type":"string"}},"required":["toEmail"],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"examples":{"sent":{"summary":"Invoice email sent","value":{"data":{"function":"send_invoice_v1a","raw":{"result":true},"result":true,"status":200},"success":true}},"sentQuote":{"summary":"Quote email sent","value":{"data":{"function":"send_invoice_v1a","raw":{"result":true},"result":true,"status":200},"success":true}}}}},"description":"Sent (or send attempted)"},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}}}},"description":"Bad Request"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}}}},"description":"Unauthorized"},"429":{"content":{"application/json":{"examples":{"rateLimited":{"summary":"Rate limited","value":{"details":{"response":{"error":{"message":"Too many requests! Please try again later","status":"RESOURCE_EXHAUSTED"}},"status":429},"error":"Too many requests! Please try again later","success":false}}}}},"description":"Too Many Requests"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"},"503":{"content":{"application/json":{"examples":{"temporarilyUnavailable":{"summary":"Service temporarily unavailable","value":{"error":"This action is temporarily unavailable.","success":false}}}}},"description":"Service Unavailable"}},"summary":"Send invoice, quote, or reminder by email","tags":["Invoices"]}},"/invoicing-settings":{"get":{"description":"Returns invoicing-related account settings including bank details and invoice defaults.","responses":{"200":{"content":{"application/json":{"examples":{"settings":{"summary":"Invoicing settings","value":{"data":{"bankData":{"iban":"CH0700700112900411647","name":"My Company GmbH","vatEnabled":true,"vatMode":"additive","vatTaxMethod":"effective"},"company":{"banks":[{"currency":"CHF","iban":"CH0700700112900411647","id":"bank_chf","name":"Default","type":"iban"}],"name":"My Company GmbH","vatEnabled":true,"vatMode":"additive","vatTaxMethod":"effective"},"email":"user@example.com","locale":"en","settings":{"invoiceDefaultDescription":"Thank you for your business.","invoicePaymentDueInDays":30,"lastUsedVatRate":8.1}},"success":true}}}}},"description":"OK"},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}}}},"description":"Bad Request"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}}}},"description":"Unauthorized"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"}},"summary":"Get invoicing settings","tags":["Invoicing Settings"]},"patch":{"description":"Partially updates invoicing settings. Only the fields you include will be changed; omitted fields remain unchanged.","requestBody":{"content":{"application/json":{"examples":{"flatTaxMode":{"summary":"Flat VAT tax method","value":{"company":{"vatEnabled":true,"vatMode":"additive","vatNumber":"CHE-123.456.789","vatTaxMethod":"flat"},"settings":{"flatVatTaxRate":6.1,"invoiceDefaultDescription":"Merci pour votre confiance."}}},"fullVatSetup":{"summary":"Bank details + VAT settings","value":{"company":{"banks":[{"currency":"CHF","iban":"CH0700700112900411647","id":"bank_chf","name":"Default","type":"iban"}],"buildingNumber":"12","city":"Z\u00fcrich","country":"CH","name":"My Company GmbH","street":"Bahnhofstrasse","vatEnabled":true,"vatMode":"additive","vatNumber":"CHE-123.456.789","vatTaxMethod":"effective","zip":"8001"},"settings":{"allowCustomVatRate":true,"invoiceDefaultDescription":"Thank you for your business.","invoicePaymentDueInDays":30,"invoicePaymentReference":"Customer reference","lastUsedVatRate":8.1}}},"paymentTerms":{"summary":"Payment terms and reply-to","value":{"settings":{"invoicePaymentDueInDays":14,"invoicePaymentReference":"PO / customer ref","invoiceReplyToEmail":"billing@mycompany.ch"}}},"quoteDefaults":{"summary":"Quote defaults only","value":{"locale":"en","settings":{"invoiceDefaultQuoteDescription":"This quote is valid for 30 days.","invoiceShowQuoteSignatureFields":true,"lastUsedInvoiceType":"quote"}}}},"schema":{"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"examples":{"settingsUpdated":{"summary":"Updated invoicing settings","value":{"data":{"bankData":{"iban":"CH0700700112900411647","name":"My Company GmbH","vatEnabled":true,"vatMode":"additive","vatTaxMethod":"effective"},"company":{"banks":[{"currency":"CHF","iban":"CH0700700112900411647","id":"bank_chf","name":"Default","type":"iban"}],"name":"My Company GmbH","vatEnabled":true,"vatMode":"additive","vatTaxMethod":"effective"},"email":"user@example.com","locale":"en","settings":{"invoiceDefaultDescription":"Thank you for your business.","invoicePaymentDueInDays":30,"lastUsedVatRate":8.1}},"success":true}}}}},"description":"OK"},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}}}},"description":"Bad Request"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}}}},"description":"Unauthorized"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"}},"summary":"Update invoicing settings","tags":["Invoicing Settings"]}},"/openapi.json":{"get":{"responses":{"200":{"content":{"application/json":{"examples":{"spec":{"summary":"OpenAPI document","value":{"info":{"title":"Magic Heidi Invoicing Software API","version":"1.0.0"},"openapi":"3.1.0"}}}}},"description":"OpenAPI JSON"}},"security":[],"summary":"Get OpenAPI specification (JSON)","tags":["Public"]}},"/payment-methods":{"get":{"responses":{"200":{"content":{"application/json":{"examples":{"multiplePaymentMethods":{"summary":"Multiple payment methods","value":{"data":{"count":2,"items":[{"emoji":"\ud83c\udfe6","id":"pm_bank","name":"Bank transfer","type":"payment"},{"emoji":"\ud83d\udcf1","id":"pm_twint","name":"TWINT","type":"payment"}]},"success":true}},"paymentMethods":{"summary":"Payment methods list","value":{"data":{"count":1,"items":[{"emoji":"\ud83c\udfe6","id":"pm_bank","name":"Bank transfer","type":"payment"}]},"success":true}}}}},"description":"OK"},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}}}},"description":"Bad Request"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}}}},"description":"Unauthorized"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"}},"summary":"List payment methods","tags":["Payment Methods"]},"post":{"requestBody":{"content":{"application/json":{"examples":{"bankTransfer":{"summary":"Bank transfer","value":{"emoji":"\ud83c\udfe6","name":"Bank transfer"}},"cash":{"summary":"Cash","value":{"emoji":"\ud83d\udcb5","name":"Cash"}},"creditCard":{"summary":"Credit card","value":{"emoji":"\ud83d\udcb3","name":"Credit Card"}},"twint":{"summary":"TWINT","value":{"emoji":"\ud83d\udcf1","name":"TWINT"}}},"schema":{"type":"object"}}},"required":true},"responses":{"201":{"content":{"application/json":{"examples":{"createdPaymentMethod":{"summary":"Payment method created","value":{"data":{"emoji":"\ud83c\udfe6","id":"pm_bank","name":"Bank transfer","type":"payment"},"success":true}},"createdTwint":{"summary":"TWINT created","value":{"data":{"emoji":"\ud83d\udcf1","id":"pm_twint","name":"TWINT","type":"payment"},"success":true}}}}},"description":"Created"},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}}}},"description":"Bad Request"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}}}},"description":"Unauthorized"},"409":{"content":{"application/json":{"examples":{"alreadyExists":{"summary":"Document ID already exists","value":{"error":"Document 'abc123' already exists","success":false}}}}},"description":"Conflict"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"}},"summary":"Create payment method","tags":["Payment Methods"]}},"/payment-methods/{id}":{"delete":{"responses":{"200":{"content":{"application/json":{"examples":{"deleted":{"summary":"Deleted payment method","value":{"data":{"deleted":true,"id":"pm_bank"},"success":true}}}}},"description":"OK"},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}}}},"description":"Bad Request"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"examples":{"notFound":{"summary":"Document not found","value":{"error":"Document not found","success":false}}}}},"description":"Not Found"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"}},"summary":"Delete payment method","tags":["Payment Methods"]},"get":{"responses":{"200":{"content":{"application/json":{"examples":{"paymentMethod":{"summary":"Payment method","value":{"data":{"emoji":"\ud83c\udfe6","id":"pm_bank","name":"Bank transfer","type":"payment"},"success":true}}}}},"description":"OK"},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}}}},"description":"Bad Request"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"examples":{"notFound":{"summary":"Document not found","value":{"error":"Document not found","success":false}}}}},"description":"Not Found"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"}},"summary":"Get payment method","tags":["Payment Methods"]},"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"patch":{"requestBody":{"content":{"application/json":{"examples":{"emoji":{"summary":"Set emoji","value":{"emoji":"\ud83c\udfe6"}},"rename":{"summary":"Rename payment method","value":{"name":"Bank Transfer"}},"renameCard":{"summary":"Rename card","value":{"name":"Corporate Card"}},"twint":{"summary":"Set TWINT name","value":{"name":"TWINT"}}},"schema":{"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"examples":{"paymentMethod":{"summary":"Payment method","value":{"data":{"emoji":"\ud83c\udfe6","id":"pm_bank","name":"Bank transfer","type":"payment"},"success":true}}}}},"description":"OK"},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}}}},"description":"Bad Request"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"examples":{"notFound":{"summary":"Document not found","value":{"error":"Document not found","success":false}}}}},"description":"Not Found"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"}},"summary":"Update payment method","tags":["Payment Methods"]}},"/signout":{"post":{"description":"Revokes Firebase refresh tokens for the authenticated Magic Heidi account, forcing all signed-in clients for that account to authenticate again.","responses":{"200":{"content":{"application/json":{"examples":{"signedOut":{"summary":"All sessions revoked","value":{"data":{"message":"Signed out all users"},"success":true}}},"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}},"description":"Signed out"},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}}}},"description":"Bad Request"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}},"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}},"description":"Invalid API key"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"},"502":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}},"description":"Could not sign out users"}},"summary":"Sign out all users","tags":["Auth"]}},"/statements/import":{"post":{"description":"One upload, both halves of the bank statement. Outgoing debits go through the expense importer (`/expenses/import`) and incoming credits through the invoice importer (`/invoices/import`) \u2014 settling existing unpaid invoices first, then creating invoices for the revenue that has none. This is the endpoint to use for any bank file; the per-entity import endpoints remain available and unchanged. Each half runs independently, so a failure in one still returns the other, and both can be disabled per request.","requestBody":{"content":{"multipart/form-data":{"schema":{"properties":{"createInvoices":{"description":"Set to `false` to skip the invoice half. Defaults to true.","type":"string"},"dryRun":{"description":"Set to `true` to report what would be imported without writing anything.","type":"string"},"files":{"description":"One or more CSV/Excel/CAMT bank-statement files (repeat the field for multiple files).","format":"binary","type":"string"},"importExpenses":{"description":"Set to `false` to skip the expense half. Defaults to true.","type":"string"},"locale":{"description":"Locale hint for AI text (e.g. `en`, `de`, `fr`). Defaults to the user's profile locale.","type":"string"},"markPaid":{"description":"Set to `false` to record payments on matched invoices without flipping `isPaid`. Defaults to true.","type":"string"}},"required":["files"],"type":"object"}}},"required":true},"responses":{"201":{"content":{"application/json":{"examples":{"importSummary":{"summary":"Successful import","value":{"dryRun":false,"expenses":{"ai_failures":0,"dropped_invalid":0,"dry_run":false,"errors":[],"imported":12,"skipped_by_ai":7,"skipped_credits":0,"skipped_duplicate":0,"skipped_post_ai_duplicate":0,"skipped_pre_ai_duplicate":0,"structure_source":"heuristic","success":true,"total_rows":19},"invoices":{"ai_failures":0,"created":[{"amount":39.4,"clientCreated":true,"clientId":"cli_1","clientName":"Sumup","currency":"CHF","date":"2026-01-30","importRef":"camt:8f2a1c\u2026","invoiceId":"abc123","itemName":"Versement Sumup du 30.01.2026","kind":"acquirer_payout","version":41}],"dry_run":false,"errors":[],"imported":91,"matched":{"applied":[],"markedPaid":2,"partialPayments":0,"suggestions":[]},"nonInvoiceable":[{"amount":220.0,"counterparty":"","date":"2026-01-07","description":"D\u00e9p\u00f4t bancomat CHF 220.00","reason":"card_transaction"}],"skippedDuplicates":[],"skipped_duplicate":0,"success":true,"total_rows":123,"unclassified":[{"aiReason":"identifiable payer but no reference to a service","amount":70.0,"counterparty":"Oscar Jon Hamilton","date":"2026-07-24","description":"Cr\u00e9dit Instant Payment Oscar Jon Hamilton"}]},"parseErrors":[],"summary":{"duplicatesSkipped":0,"expensesImported":131,"files":1,"invoicesCreated":91,"markedPaid":2,"nonInvoiceable":29,"unclassified":2}}}}}},"description":"Statement processed; returns a combined summary plus the full result of each half."},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}}}},"description":"Missing file field"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}}}},"description":"Unauthorized"},"422":{"description":"Nothing could be imported from the uploaded file(s)"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"}},"summary":"Import a bank statement completely: expenses and invoices in one call","tags":["Invoices"]}},"/v1/subscription":{"get":{"description":"Returns the current user's subscription status, platform, and period information. Uses RevenueCat to determine whether the user is a web/Stripe, iOS, or Android subscriber.","responses":{"200":{"content":{"application/json":{"examples":{"activeAndroidSubscription":{"summary":"Active Android subscription","value":{"data":{"email":"user@example.com","entitlement":"pro","expiresDate":"2025-12-01T08:00:00Z","platform":"android","productIdentifier":"mh_pro_monthly","purchaseDate":"2025-11-01T08:00:00Z","subscribed":true},"success":true}},"activeIosSubscription":{"summary":"Active iOS subscription","value":{"data":{"email":"user@example.com","entitlement":"pro","expiresDate":"2026-09-01T12:00:00Z","platform":"ios","productIdentifier":"mh_pro_yearly","purchaseDate":"2025-09-01T12:00:00Z","subscribed":true},"success":true}},"activeWebSubscription":{"summary":"Active web subscription","value":{"data":{"email":"user@example.com","entitlement":"pro","expiresDate":"2099-12-31T23:59:59Z","platform":"web","productIdentifier":"rc_promo_pro_lifetime","purchaseDate":"2025-06-15T10:30:00Z","subscribed":true},"success":true}},"notSubscribed":{"summary":"Not subscribed","value":{"data":{"email":"user@example.com","entitlement":null,"expiresDate":null,"platform":null,"productIdentifier":null,"purchaseDate":null,"subscribed":false},"success":true}}}}},"description":"OK"},"400":{"content":{"application/json":{"examples":{"invalidJson":{"summary":"Invalid request body","value":{"error":"Request body must be JSON","success":false}},"validationError":{"summary":"Validation error","value":{"error":"`currency` must be a 3-letter ISO 4217 code","success":false}}}}},"description":"Bad Request"},"401":{"content":{"application/json":{"examples":{"invalidApiKey":{"summary":"Invalid API key","value":{"error":"Invalid API key","success":false}},"missingApiKey":{"summary":"Missing API key","value":{"error":"Missing API key. Use header 'X-API-Key' (or 'key').","success":false}}}}},"description":"Unauthorized"},"500":{"content":{"application/json":{"examples":{"serverError":{"summary":"Internal error","value":{"error":"Internal server error","success":false}}}}},"description":"Internal Server Error"},"502":{"content":{"application/json":{"examples":{"revenueCatError":{"summary":"RevenueCat unavailable","value":{"error":"Could not retrieve subscription information","success":false}}}}},"description":"Bad Gateway"}},"summary":"Get subscription status","tags":["Subscription"]}}},"security":[{"ApiKeyHeader":[]}],"servers":[{"url":"/v1"}],"tags":[{"description":"API documentation and health check","name":"Public"},{"description":"Account authentication and session management","name":"Auth"},{"description":"Company profile, bank details, VAT configuration, and invoice defaults","name":"Invoicing Settings"},{"description":"Invoice payment accounts stored in company.banks (separate from transaction-import bank connections)","name":"Bank Accounts"},{"description":"Manage client records used as recipients on invoices and quotes","name":"Clients"},{"description":"Create, generate, send, and manage invoices and quotes","name":"Invoices"},{"description":"Reusable products, articles, and services that can be added as line items on invoices","name":"Invoice Items (Products/Articles)"},{"description":"Unified collection of both expense categories and payment methods","name":"Categories"},{"description":"Payment methods available for recording payments on invoices and expenses (e.g. Bank transfer, Credit Card, TWINT)","name":"Payment Methods"},{"description":"Categories for classifying expenses (e.g. Travel, Software, Office)","name":"Expense Categories"},{"description":"Track and manage business expenses, with optional receipt scanning","name":"Expenses"},{"description":"Export yearly accounting data as a backup archive","name":"Backups"},{"description":"Swiss company (Zefix) directory lookup for auto-completing company and client details","name":"Companies"},{"description":"Connect a user's bank via SIX bLink (open banking) and fetch their account transactions","name":"Bank Connections"}]}
