Keep your users updated as your project evolves

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.

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.

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.

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.

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 draftconfirmed (number minted) → fulfilled (system transition once every ordered line is invoiced, reversible if the invoiced total drops), plus rejected, reopened, and canceled.

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.

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.