Skip to content

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).

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).

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

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 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 vats when you send it, are used as sent, so they must add up:

  • vats needs articleRows in the same request. Send one entry per VAT percentage with percentage and totalVat; Quiddly computes the taxable amount per percentage from the rows.
  • The e-invoice takes the invoice’s total VAT from totals.totalVat and the VAT per rate from vats. Make totals.totalVat equal the sum of the vats entries. When the VAT per row rounds differently, send vats explicitly with the same total as totals.totalVat.
  • Let totalExclVat plus totalVat plus totalRoundOff equal total. The e-invoice derives its rounding amount as total minus totalExclVat minus totalVat, and the ledger books totalRoundOff as its own posting when it is not zero, so put any rounding difference in totalRoundOff.

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. or Payment 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.

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.

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.

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.

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_FINANCING

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.

Terminal window
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"
  1. 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. Omit clientId if you authenticate with a client token. Add ?returnResource=false to get just { "id": "I...." } back.

  2. Activate it. Requires ActivateInvoices. Choose the product here - INVOICE_SERVICE for a plain service invoice, or INVOICE_FACTORING_SELL to send it for financing directly (see below). If you omit productCode the 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 on ACTIVATED with bookedDate set. Subscribe to the INVOICE / INVOICE_BALANCE webhooks instead of polling.

    The productCode you send decides the product. With INVOICE_SERVICE the invoice stays a service invoice; an integration that always sends INVOICE_SERVICE always ends up on service. With INVOICE_FACTORING_SELL the invoice is activated for financing directly: it goes to PENDING_AUTHORIZATION and waits for the financing decision, so step 3 is not needed.

  3. (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 be ACTIVATED and product INVOICE_SERVICE, and the debtor must not be blocked for factoring. See the factoring decision flow below.

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.

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 becomes DECIDED. The invoice stays ACTIVATED.
  • Denied: main status becomes DENIED_FINANCING. Do not send DENIED for an invoice whose product is INVOICE_SERVICE. The call does not complete. Use DENIED_SERVICE for it.
  • Denied as service (DENIED_SERVICE): financing is denied but the invoice continues as a service invoice (product INVOICE_SERVICE), is activated and is distributed.

Track the decision with GET /factoring-decisions/invoice/<id> and the FACTORING_STATUS webhook.

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.