Skip to navigation
Keep your users updated as your project evolves

Notable changes to the Lucanto API — new endpoints, fields, and breaking changes. Filter entries by tag and subscribe via RSS.

Changed: invoice PDFs are ISDOC.PDF where ISDOC applies

GET /workspaces/{workspace_id}/invoices/{id}/pdf now returns an ISDOC.PDF for an issued invoice in a Slovak or Czech workspace: a PDF/A-3 that looks and prints like before, with the ISDOC XML embedded so accounting software such as POHODA, KROS or Money can import it. The response is still application/pdf; the filename ends in .isdoc.pdf.

Drafts, cancelled invoices, other workspace countries, and any invoice whose ISDOC cannot be built get the plain PDF, named .pdf, as before. ?variant=plain forces the plain PDF, and plain is the only accepted value. The ETag now differs per variant, so a cached plain PDF is not served as the default. The MCP lucanto_get_invoice_pdf_url tool names the file the same way.

documentsmcp

Added: German (de) as a user and document language

language on GET /user and PATCH /user now accepts and returns de. Unlike uk, German is also a document language: pdf_language on invoices, credit notes, proformas and quotes, the locale on the send-e-mail bodies, and the document-settings defaults accept de, so PDFs and customer e-mails can be rendered in German.

documentsuserauthorizationmcp

Added: manual reminders for proformas

POST /workspaces/{workspace_id}/proformas/{id}/send_reminder sends a payment reminder for an issued, unpaid proforma, like the invoice endpoint. It needs the manage:proformas scope and the same plan and trial gates as send.

documentsuserauthorizationmcp

Changed: reminders are queued and limited to one per document per UTC day

POST …/invoices/{id}/send_reminder and the new proforma endpoint no longer send the e-mail during the request. A 200 means the reminder was accepted and queued; reminder_count and last_reminded_at are updated once it is actually sent, so the document in the response does not show them incremented yet. The limit is one reminder per document per UTC calendar day, replacing the rolling 24-hour window; a second call the same day returns 429 with retry_after_seconds counting down to UTC midnight.

documentsuserauthorizationmcp

Changed: mark_paid defaults paid_on to the workspace's day

When paid_on is omitted on POST …/invoices/{id}/mark_paid or POST …/proformas/{id}/mark_paid, the payment is dated today in the workspace’s time_zone rather than the server’s UTC date. A payment recorded shortly after midnight local time now lands on the correct day.

documentsuserauthorizationmcp

Added: fifty MCP tools across documents, expenses, banking and projects

The MCP server now covers quotes, proformas and credit notes (list, get, create, update), sales orders (list, get), expenses (list, get, create, update), projects (list, get, create, update), contacts (get, update), document settings (get, update), and read-only bank accounts, transactions and loans. Each tool needs the matching resource scope; lifecycle actions for types other than invoices stay on the REST API. The full catalogue is on the MCP server page.

documentsuserauthorizationmcp

Added: read:loans scope

Tokens can carry read:loans, used by the MCP loan tools. Loans are read-only: write:loans and manage:loans are accepted but grant only read access.

documentsuserauthorizationmcp

Added: Claude.ai sign-in to the MCP server with OAuth 2.1

Claude.ai (web, mobile and Cowork) connects to https://app.lucanto.eu/api/mcp/v1 by signing the user in rather than using a pasted token. The user approves the requested access on a consent screen, capped by their workspace role, and the connection appears under Settings > Personal API access, where it can be revoked. OAuth access tokens work only on the MCP endpoint, not on the REST API; personal access tokens keep working on both.

documentsuserauthorizationmcp

Added: Ukrainian (uk) as a user language

language on GET /user and PATCH /user now accepts and returns uk alongside sk, cz, en and ro. It sets the language of the app and of the e-mails Lucanto sends to that user.

Document languages are unchanged: pdf_language on invoices, credit notes, proformas and quotes, and locale on the send-e-mail bodies and the MCP lucanto_send_invoice tool, still take sk, cz, en or ro. A user whose language is uk receives PDFs and customer e-mails in the document’s own language; a document without one falls back to the workspace’s country language, never to the user’s UI language.

user

Breaking: create/update bodies for invoices, credit notes, proformas and quotes reject unknown fields

InvoiceWritable, CreditNoteWritable, ProformaWritable, QuoteWritable, their LineItemInput shapes, and the *SendRequest bodies (including the nested invoice_email / credit_note_email / etc. objects) now declare additionalProperties: false. A request carrying a key outside the documented schema is rejected with 422 instead of being silently ignored. The legacy { "document": { ... } } wrapper and the Rails-style indexed line_items object ({"0": {...}}) are still accepted by the server for backward compatibility, but neither is part of the documented schema — send a flat top-level body with line_items as an array. File uploads (attachments) travel as multipart form fields, outside this JSON body.

breakingdocumentsapi-keysall-resources

Breaking: line-item removal uses a boolean _destroy

_destroy on InvoiceLineItemInput, CreditNoteLineItemInput, ProformaLineItemInput, and QuoteLineItemInput is now type: boolean. The previous "true"/"false" string enum is no longer part of the documented schema — send true to remove a line item on update.

breakingdocumentsapi-keysall-resources

Breaking: mark_paid.amount is a decimal string with documented over/under-payment semantics

amount on POST …/invoices/{id}/mark_paid and POST …/proformas/{id}/mark_paid must match ^\d+(\.\d{1,2})?$ — no thousands separators, no comma decimal separator. Omitting it, or sending an explicit null, settles the whole outstanding balance and payment_status derives to paid. A smaller amount records a partial payment: payment_status becomes partially_paid and amount_due drops by that amount, so you can call the endpoint again for the next instalment. An amount above the outstanding balance is accepted and recorded in full, and payment_status then derives to overpaid.

breakingdocumentsapi-keysall-resources

Breaking: MCP lucanto_send_invoice follows the plan and trial gates

Sending an invoice by email over the MCP server is now gated the same way as the web app and the REST API. On a plan without email sending the tool returns a subscription error with reason: "feature_not_available"; a trial workspace can send one document email per day and gets reason: "trial_send_cap" once that is used up. The daily counter is shared across all three surfaces, and a request that fails validation does not consume it.

breakingdocumentsapi-keysall-resources

Added: read-only sales orders API

GET /workspaces/{workspace_id}/sales_orders and GET …/sales_orders/{id} return a customer’s orders to you, tracked until fully invoiced. Both need the read:sales_orders scope; the resource is read-only, so the token scope picker offers it at the read level only. Reference the id as sales_order_id on an invoice or proforma to record which order it fulfils. A sales order carries no payment instructions: payment_status, amount_paid, and amount_due are always null, and overdue/paid_late are always null too. status moves through draft → confirmed (number minted) → fulfilled (system transition once every ordered line is invoiced, reversible if the invoiced total drops), plus rejected, reopened, and canceled.

breakingdocumentsapi-keysall-resources

Added: delivery date, texts and item descriptions over MCP

lucanto_create_invoice and lucanto_update_invoice now accept delivery_date, opening_text, closing_text, and a per-item description, matching the REST create and update bodies. Invoices imported through an MCP client no longer lose these fields on the way in.

breakingdocumentsapi-keysall-resources

Fixed: issuing without a number series, email language, and mark_paid errors

  • POST …/invoices/{id}/issue and the MCP lucanto_issue_invoice tool now answer 422 with “No number series is set up for this document type. Supply a document number or create a number series.” instead of a generic transition error when the workspace has no series for that document type.
  • Document emails sent over the API and MCP are rendered in the document’s language; they no longer fall back to English. An explicit locale on the send request still overrides it, and locale: null keeps the default.
  • mark_paid’s 422 now names an invalid amount (not a decimal string with at most two decimals) as a documented failure cause, alongside no outstanding balance, a bank_account_id outside the workspace, and a transaction that fails validation.
breakingdocumentsapi-keysall-resources

Statutory exchange-rate anchors, and ČNB rates for Czech workspaces

Automatically fetched exchange_rate values on foreign-currency documents and expenses now follow the statutory anchor: the case day is the delivery date (falling back to the issue date), valued for Slovak workspaces at the ECB reference rate declared the preceding day, and for Czech workspaces at the ČNB rate for the case day itself. Czech workspaces can also elect the D−1 ČNB rate or a fixed rate list (pevný kurz) per workspace, and the settings screen generates the matching internal directive text.

breakingexpensesdocumentstransactions

Breaking: document status is now lifecycle-only; payment state moves to payment_status

The single status enum on invoices, proformas, credit notes, and quotes no longer mixes lifecycle and payment. status now carries only the document’s lifecycle, with a per-type value set:

  • invoices, credit notes: draft, issued, sent, canceled
  • proformas: adds settled and expired
  • quotes: draft, issued, sent, accepted, rejected, expired, canceled
breakingexpensesdocumentstransactions

Breaking: status is no longer writable — use action endpoints

Create and update bodies no longer accept a status field. Documents are moved through their lifecycle with the action endpoints — POST …/invoices/{id}/issue, POST …/credit_notes/{id}/issue, POST …/quotes/{id}/accept — and payment is recorded, not declared: POST …/invoices/{id}/mark_paid creates a real payment record for the outstanding balance, and payment_status derives from it.

breakingexpensesdocumentstransactions

Settle documents across currencies

POST /workspaces/{workspace_id}/transactions/{id}/pair now accepts document_amount — the settled amount in the paired record’s own currency — so a transaction can settle a document or expense held in a different currency (a EUR card charge paying a USD invoice). The allocation stores both legs: the bank amount in the transaction’s currency and the settled amount in the document’s, and payment status derives from the settled side. Decimal input is locale-tolerant (72,90 and 72.90 both parse).

transactionsexpensesdocuments