Create & Book Invoice
Issuing an invoice is two stages: create it (status CREATED), then
activate it. Activation posts the invoice into the ledger and is what
“booked” means - the invoice reaches status ACTIVATED with a bookedDate.
Factoring adds a credit-decision step on top of the same path.
POST /invoices takes the structured createInvoice object (nested debtor,
dates, articleRows, currency), not a flat amount. Required right:
CreateInvoices. There is also a machine-to-machine variant,
POST /invoices/integration (permission invoice:create), for token-based
integrations. Such an integration uploads invoice PDFs as described in
Upload a PDF (machine-to-machine).
Request payload
Section titled “Request payload”Top level
Section titled “Top level”| Field | Required | Notes |
|---|---|---|
invoiceNumber |
yes | Your invoice number. Must be unique per client, credit invoices included (see Invoice numbers and payment references). Leading and trailing spaces are removed. |
articleRows |
yes | List of line items (see below). |
debtor |
yes | Nested debtor object (see below). The debtor is matched/created automatically - no separate debtor call. |
dates |
yes | Nested object with invoiceDate and expireDate. |
totals |
yes | Nested object with total, totalExclVat and totalVat; totalRoundOff is optional. A request without them is refused with Required field 'totals.total' missing (see Totals and VAT must agree). |
clientId |
conditional | Top-level. Required with a system token (scopes the invoice to a client). Omit it with a client token - it is taken from the token. |
currency |
no | Nested object; see Currency below. Defaults to the tenant’s configured currency. |
type |
no | INVOICE, CREDIT_INVOICE, etc. Defaults to a standard invoice. |
serviceProviderCode |
no | Which installed service provider administers the invoice. |
paymentReference |
no | Normally generated by Quiddly. Only send it when the client is allowed to set its own reference; it must be unique per client (see Invoice numbers and payment references). |
terms.payment |
no | Payment terms text. Printed on the invoice document exactly as sent (see Payment terms). |
distributionCode |
no | Channel to start distribution on. Can also be set at activation (see Distribution). |
disableFallbackDistribution |
no | true stops Quiddly from trying another channel when this one fails (see Distribution). |
productCode is not set here - it is chosen at activation (step 2).
debtor (matched by customerNumber)
Section titled “debtor (matched by customerNumber)”| Field | Required | Notes |
|---|---|---|
customerNumber |
yes | Your customer number for the debtor. This is the matching key - Quiddly maps it to an existing debtor or creates one. |
name |
yes | Person or company name. |
type |
yes | COMPANY or PERSON. |
identificationNumber |
no | Org/SSN. Recommended; improves matching and is needed for collection and for financing. Validated against the debtor’s locale (see Identification number and locale). |
locale |
no | The debtor’s legal country: a two-letter country code, or OTHER. Shown as Legal Country in the portals. If omitted it is taken from the debtor already matched by customerNumber. Not the same as invoiceAddress.countryCode. |
email, phone, invoiceAddress, deliveryAddress, peppolId, … |
no | Contact and routing details. |
FMI |
no | Recipient address for the consumer bank e-invoice network (Swedish FMI, Norwegian eFakturaadresse). Only relevant when type is PERSON and the invoice goes out as ELECTRONIC_INVOICE. Leave it out to have it looked up from identificationNumber against the register for the debtor’s country. |
| Field | Required |
|---|---|
invoiceDate |
yes |
expireDate |
yes |
deliveryDate |
no |
articleRows
Section titled “articleRows”The list is required. Individual rows have no single mandatory field - send the
ones you have (description, quantity, unit, price, vatPercentage,
articleNumber, accountNumber, …). Alternatively set
articleRowsGenerate: true to let Quiddly compute the rows/totals from the
invoice data.
Currency
Section titled “Currency”Currency is set inside the nested currency object, not at the top level:
"currency": { "invoiceCurrency": "SEK", "baseCurrency": "SEK" }invoiceCurrency is what the PDF shows; baseCurrency (optional) is the
currency the invoice is handled in. If no baseCurrency is sent
Quiddly defaults to the system currency. baseCurrency will decide which client
account it will be handled in. totals is required (total, totalExclVat and
totalVat). The VAT breakdown (vats) is optional: when you leave it out, Quiddly builds it
from the article rows.
Totals and VAT must agree
Section titled “Totals and VAT must agree”totals, and vats when you send it, are used as sent, so they must add up:
vatsneedsarticleRowsin the same request. Send one entry per VAT percentage withpercentageandtotalVat; Quiddly computes the taxable amount per percentage from the rows.- The e-invoice takes the invoice’s total VAT from
totals.totalVatand the VAT per rate fromvats. Maketotals.totalVatequal the sum of thevatsentries. When the VAT per row rounds differently, sendvatsexplicitly with the same total astotals.totalVat. - Let
totalExclVatplustotalVatplustotalRoundOffequaltotal. The e-invoice derives its rounding amount astotalminustotalExclVatminustotalVat, and the ledger bookstotalRoundOffas its own posting when it is not zero, so put any rounding difference intotalRoundOff.
Invoice numbers and payment references
Section titled “Invoice numbers and payment references”invoiceNumber is unique per client, and credit invoices share the same
numbering as invoices. The uniqueness is enforced in the database, so it holds
for every way of creating an invoice. Creating an invoice with a number that
the client already uses fails with HTTP 409 and the message
Entity with provided keys: <invoiceNumber>, <clientId> already exists.
Quiddly does not add a suffix. If your system reuses a number, for example when
a collection claim is created through the API for an invoice that already
exists, make the number unique yourself, for instance with a running suffix
(-1, -2).
paymentReference is normally generated by Quiddly when the invoice is
activated. If you send your own:
- The client must be allowed to set it, through a client setting or the
setting on the payment account. Otherwise activation is rejected with
Payment reference was defined but payment account does not allow it.orPayment reference was defined but client setting does not allow it. - A tenant can be configured to ignore a reference sent on creation for clients that are not allowed to set one. The invoice then gets a system note saying which reference was ignored, and Quiddly generates one.
- The reference must be unique per client, credit invoices included. A
duplicate is rejected at activation with
Invoice with payment reference <reference> already exists.
Payment terms
Section titled “Payment terms”The payment terms text printed on the invoice document is exactly the value you
send in terms.payment, under the heading Payment Terms (the heading is
translated to the document language, the value is not). Quiddly does not add
a unit such as “days”. Send the final text in the debtor’s
language, or a bare number used consistently across your invoices. The
dates.expireDate is what the e-invoice carries as payment terms.
Identification number and locale
Section titled “Identification number and locale”debtor.identificationNumber is checked against the debtor’s locale:
| Locale | What is checked |
|---|---|
SE |
Non-digits such as dashes are removed. The number must have 10 or 12 digits and a valid check digit (Luhn). An invalid number does not fail the request: the invoice is created with identificationNumber stored as null. |
NO |
9 digits (organisation) or 11 digits (person). An invalid format rejects the request with <value> is invalid identification number. |
FI |
The Finnish organisation and person formats. An invalid format rejects the request with the same message. |
any other value, including OTHER |
Not validated, the number is stored as sent. |
Without a valid identification number the invoice cannot be financed
(Debtor identification number must be present to use factoring.), and
collection needs it as well.
debtor.locale is the legal country of the debtor, shown as Legal Country
in the Operations and Client Portals. Send a two-letter country code or
OTHER. If you omit it, the value on the debtor already matched by
customerNumber is used. Quiddly uses it to validate the identification
number, to decide which country the debtor belongs to for credit checks (with
OTHER it falls back to the country of the invoice address), and to decide
whether a letter to the debtor is an international distribution with its own
charge. It is separate from invoiceAddress.countryCode, which is where the
invoice is sent.
Distribution
Section titled “Distribution”distributionCode selects the channel the invoice is sent on. Set it at
creation or at activation (PUT /invoices/<id>/activate, or
POST /invoices/actions/bulk-activate with invoiceIds).
| Value | Channel |
|---|---|
EMAIL |
E-mail. |
LETTER |
Printed letter. |
SMS |
SMS. |
ELECTRONIC_INVOICE |
E-invoice. |
DIGITAL_MAILBOX |
Digital mailbox. |
PARTNER |
Distribution by an external partner. |
APPLY_CLIENT_DISTRIBUTION_HIERARCHY |
Not a channel for an invoice. It is only valid on reminder profile levels, where it means “use the client’s distribution order”. Do not send it when you create or activate an invoice. |
How fallback works. The invoice gets a copy of the client’s distribution
profile, starting at the channel you chose and continuing through the later
channels of the profile that are enabled, in the profile’s priority order. Each
channel is tried as many times as the profile says before the next channel is
used. A debtor with distributionChannelOverride only changes which channel
the chain starts on. Reminders are never sent as e-invoice, only by the other
channels.
disableFallbackDistribution: set it to true on an invoice (at creation,
at activation, or in bulk activation) to stop the chain after the chosen
channel. When that channel fails, no e-mail or letter is tried instead.
Quiddly creates a Failed Distribution errand for the client and sends a
DISTRIBUTION webhook with
distributionStatus FAILED.
A channel can first report an invoice as delivered or opened and report it as
failed later, for example when the recipient’s mail server bounces it
afterwards. The later failure is handled like any other failure: the next
attempt or channel in the chain, or, with disableFallbackDistribution, the
errand and webhook above.
Financed invoices. An invoice activated with a financing product waits in
PENDING_AUTHORIZATION for the financing decision. Its document is generated
and it is distributed when the decision approves it, or denies financing but
keeps it as a service invoice. To distribute earlier, call
POST /invoices/distribute with { "invoiceIds": [...] } (right
DistributeInvoices). It distributes invoices that are in
PENDING_AUTHORIZATION, have a distributionCode and are not yet
distributed, and lists the others under skipped_invoices with the reason.
There is no setting that does this automatically.
GET /invoices/<id>/transfer-text (right ReadInvoice) returns the payment
text printed on the invoice, with the payment reference filled in, as
{ "transferText": "..." }. It is read-only and meant for external
distribution partners. It answers 400 with
Invoice missing associated transfer text. when no text applies.
Status model
Section titled “Status model”The response marshals the created invoice; the main status surfaces under
statuses.main (and ledger progress under statuses.ledger).
statuses.main |
When |
|---|---|
CREATED |
Freshly created, not yet activated. |
PENDING_AUTHORIZATION |
Waiting for the financing decision after activation with a financing product (a tenant can configure this away). |
PENDING |
Activation accepted, queued. |
ACTIVATING |
Activation running (documents, ledger, financial events). |
ACTIVATED |
Booked. bookedDate is set. |
DENIED_FINANCING |
Factoring decision denied. |
PAUSED / DISPUTED / STOPPED / CLOSED / ARCHIVED |
Later lifecycle states. |
CREATED ──activate──> PENDING ──> ACTIVATING ──> ACTIVATED (booked) │ request factoring + decision ├── approved ─> ACTIVATED (financed, product INVOICE_FACTORING_SELL) └── denied ──> DENIED_FINANCINGUpload a PDF (machine-to-machine)
Section titled “Upload a PDF (machine-to-machine)”An integration that creates invoices with POST /invoices/integration uploads
PDFs for them with the routes below. Upload before you activate the
invoice. Activation renders and distributes the invoice, so a PDF uploaded
later can miss the distribution.
| Route | Permission | What it stores |
|---|---|---|
POST /documents/integration/invoice |
invoice:document:create |
Your own rendered invoice, sent instead of the PDF Quiddly renders. The client must be set up to use its own invoice PDF, otherwise the call answers 400 Upload own invoice document is not enabled on client. |
POST /documents/integration/attachments/invoice |
invoice:attachment:create |
An attachment that is added to the invoice PDF. |
Both take multipart/form-data with file (a PDF, at most 10 MB), clientId
and invoiceId, and return the stored document. clientId is required. The
invoice must be on that client and created by your integration, otherwise the
call answers 400 Invoice could not be found on client. Each route accepts
one upload per invoice. Fetch a new token after a permission is assigned to
your application.
curl -X POST "https://$HOST/documents/integration/invoice" \ -H "Authorization: Bearer $M2M_TOKEN" \ -F "file=@invoice.pdf" -F "clientId=C.100100" -F "invoiceId=$INVOICE_ID"End-to-end flow
Section titled “End-to-end flow”-
Create the invoice.
Terminal window curl -X POST "https://$HOST/invoices" \-H "Authorization: Bearer $JWT" -H "Content-Type: application/json" \-d '{"clientId": "C.100100","invoiceNumber": "2026-0001","debtor": {"customerNumber": "CUST-42","name": "Acme AB","type": "COMPANY","identificationNumber": "5560000001"},"dates": { "invoiceDate": "2026-06-01", "expireDate": "2026-06-30" },"currency": { "invoiceCurrency": "SEK" },"articleRows": [{ "description": "Consulting", "quantity": "1", "unit": "h", "price": "1250.00", "vatPercentage": 25 }],"totals": { "totalExclVat": "1250.00", "totalVat": "312.50", "total": "1562.50" }}'Returns the created invoice with
statuses.main = CREATED. OmitclientIdif you authenticate with a client token. Add?returnResource=falseto get just{ "id": "I...." }back. -
Activate it. Requires
ActivateInvoices. Choose the product here -INVOICE_SERVICEfor a plain service invoice, orINVOICE_FACTORING_SELLto send it for financing directly (see below). If you omitproductCodethe invoice’s default product is used.Terminal window curl -X PUT "https://$HOST/invoices/$INVOICE_ID/activate" \-H "Authorization: Bearer $JWT" -H "Content-Type: application/json" \-d '{ "productCode": "INVOICE_SERVICE" }'The call returns quickly with
PENDING; activation finishes asynchronously (document generation, ledger and financial events) and the invoice lands onACTIVATEDwithbookedDateset. Subscribe to theINVOICE/INVOICE_BALANCEwebhooks instead of polling.The
productCodeyou send decides the product. WithINVOICE_SERVICEthe invoice stays a service invoice; an integration that always sendsINVOICE_SERVICEalways ends up on service. WithINVOICE_FACTORING_SELLthe invoice is activated for financing directly: it goes toPENDING_AUTHORIZATIONand waits for the financing decision, so step 3 is not needed. -
(Factoring only) Request factoring on the activated service invoice. Skip this step if you activated with
INVOICE_FACTORING_SELL.Terminal window curl -X PUT "https://$HOST/factoring-decisions/$INVOICE_ID/factoring" \-H "Authorization: Bearer $JWT" -H "Content-Type: application/json" -d '{}'Requires
UpdateCreditDecisionStatus. Guards: the invoice must beACTIVATEDand productINVOICE_SERVICE, and the debtor must not be blocked for factoring. See the factoring decision flow below.
Factoring decision flow
Section titled “Factoring decision flow”The credit decision is tracked by a credit-decision status separate from
the invoice main status. Values: PENDING, READY_FOR_DECISION, WAITING,
REFINANCE, DECIDED (plus tenant-specific groupings/sub-statuses). The
credit-decision status is not the invoice main status: you cannot set the main
status through these calls, it changes as a result of the decision.
Quiddly distinguishes internal decisions (made inside Quiddly, by the engine rule or an operator) from external decisions (made by an external financing provider):
| Action | Endpoint | Right | Use |
|---|---|---|---|
| Request factoring | PUT /factoring-decisions/<invoiceId>/factoring |
UpdateCreditDecisionStatus |
Ask to factor an activated service invoice. |
| Move internal decision | PUT /factoring-decisions/<id>/move |
UpdateCreditDecisionStatus |
Advance the internal credit-decision status (+ optional sub-status grouping). |
| Record external decision | PUT /factoring-decisions/<id>/move-external |
UpdateExternalFactoringDecision |
Record an external provider’s decision: APPROVED, DENIED, or DENIED_SERVICE. See below. |
Record an external decision
Section titled “Record an external decision”PUT /factoring-decisions/<id>/move-external takes this body:
| Field | Required | Notes |
|---|---|---|
status |
yes | A credit-decision status that is enabled in the tenant, for example PENDING, READY_FOR_DECISION, WAITING or DECIDED. REFINANCE cannot be sent here. |
decision |
when status is DECIDED |
APPROVED, DENIED or DENIED_SERVICE. Ignored for every other status. This is a decision value, not a status: sending a status such as WAITING as decision is rejected with 400. |
useRecourse |
no | Whether the financing is with recourse. |
override |
no | true to approve although the engine rule validation fails and the failed criteria allow an override. |
Moving to any other status only moves the invoice on the credit-decision board
and triggers the FACTORING_STATUS webhook. Moving to DECIDED with a
decision performs the decision described below. Common 400 answers:
| Message | Cause |
|---|---|
External factoring decision cannot use status: REFINANCE. |
status was REFINANCE. |
For status DECIDED decision must be provided. |
status was DECIDED without decision. |
Invoice <id> is already financed |
The invoice has already been financed. |
Invoice (<id>) status should be PENDING_AUTHORIZATION. |
A financing decision on an invoice that is not waiting for one. |
Status <status> does not exist. / Status <status> is disabled. |
The status is unknown or switched off in the tenant. |
Invoice failed engine rule validation. |
APPROVED failed the engine rules; the response lists the failed criteria. |
The response is the updated invoice, except after a decision, see Response after a financing decision.
The decision resolves to one of FinanceDecision = APPROVED, DENIED,
DENIED_SERVICE, REFINANCE:
- Approved: the product switches to
INVOICE_FACTORING_SELL, financed financial events are generated, the invoice is re-booked as financed, and the credit-decision status becomesDECIDED. The invoice staysACTIVATED. - Denied: main status becomes
DENIED_FINANCING. Do not sendDENIEDfor an invoice whose product isINVOICE_SERVICE. The call does not complete. UseDENIED_SERVICEfor it. - Denied as service (
DENIED_SERVICE): financing is denied but the invoice continues as a service invoice (productINVOICE_SERVICE), is activated and is distributed.
Track the decision with GET /factoring-decisions/invoice/<id> and the
FACTORING_STATUS webhook.
Response after a financing decision
Section titled “Response after a financing decision”Whether a financing decision call answers with the full invoice or an empty
body is controlled by the system setting RETURN_INVOICE_FINANCING_REQUEST_DATA.
It is a system-wide setting, not a field in the request, so contact Quiddly
support if you need it changed. Off, the default, the call returns 200 with the full
invoice. On, it returns 200 with an empty body. Nothing else changes: the
decision, saving, webhooks and the jobs that follow activation behave the same
either way, so do not read the empty body as a failure.