dunning_state reports where an invoice stands. It is held from submission
until release, then the state of the dunning process (pending, active,
paused, completed, inkasso), and finally the terminal value (paid,
cancelled, written_off).
Held is a deliberate stop, not a queue
A held invoice has no dunning process at all. Nothing advances it: not a background sweep, not activating the dunning flow in the portal, not another invoice of the same debtor. OnlyPOST /invoices/{uuid}/release/ creates the
process, and that call is the moment paywise starts writing to the debtor.
This is what makes a two-system integration safe. You can mirror your whole
open-item list into paywise without committing to dunning any of it, decide
per invoice, and release on your own schedule.
Submission is idempotent, so correction has its own verb
POST /invoices/ is idempotent on invoice_number within your company. A
repeat submission of a number that already exists returns 200 with the
existing invoice and applies none of the fields you sent — the body must
still be well-formed, but differing values are ignored rather than compared
with the stored invoice; a genuinely new number returns 201. Retrying
an ambiguous POST is therefore always safe. The replay stops where the
number no longer names an open receivable: a number that belongs to a
cancelled, written-off, or archived invoice is 409 invoice_cancelled,
invoice_written_off, or invoice_archived — submit the corrected invoice
under a new invoice number. Paid, open, and overdue invoices keep the 200
replay. The Mahnservice API does not read an Idempotency-Key header.
The consequence is that resubmitting is not a way to change anything. To
correct an invoice you already submitted, use PATCH while it is still held:
PATCH is partial: only the fields you send change. invoice_number,
amount, currency, due_date, document_date, your_reference and
debtor are correctable. The company and the credential context are never
writable, and dunning_state, archived, has_document and balance change
only through their own endpoints. There is no PUT and no DELETE: a
whole-body replace would make omitting a field destructive, which is the
opposite of what a correction wants.
Do not invent a second invoice number to fix a typo. The invoice number is
your own, from your accounting system, and paywise prints it in the notices the
debtor receives. Correcting the record in place keeps your books and the
debtor’s correspondence on the same number.
Correcting invoice_number itself is allowed, and it moves the idempotency key
with it: after renaming RE-1 to RE-2, a submission of RE-1 creates a new
invoice and a submission of RE-2 is the idempotent repeat. Renaming onto a
number that already exists is refused with 409 duplicate_invoice_number, and
nothing is changed.
Correcting due_date re-derives whether the invoice counts as open or overdue,
exactly as the same date would have at submission.
Input field rules
These rules apply when writing Mahnservice debtors, invoices, and payments. See Requests and responses for the shared JSON and error conventions.The correction window closes at release
PATCH answers 409 once the invoice has left the held state:
The invoice PDF has the same window:
POST /invoices/{uuid}/documents/
attaches or replaces it while the invoice is held, and answers 409 already_released afterwards; on a settled or archived invoice it answers
409 invoice_paid, invoice_cancelled, invoice_written_off, or
invoice_archived, on a claim in debt collection 409 invoice_in_inkasso,
and on a bookkeeping invoice 409 not_api_invoice. The document is enclosed
with the dunning notices, so it is fixed at the moment the first notice can
go out. A PATCH whose values equal the stored ones writes nothing:
updated stays put and no history entry is recorded.
The upload responds 201 with a document whose status is pending.
Poll its detail endpoint until it is ready, rejected or failed.
has_document on the invoice is only true for a ready document. An attached
document that is not ready blocks release with 409 document_not_ready;
releasing without an attachment is allowed. See
Invoice documents for the
response shape, download route and replacement workflow.
Release
POST /invoices/{uuid}/release/ creates the dunning process. It is idempotent:
releasing an already-released invoice returns the current state rather than
starting a second ladder.
The process starts active when the company’s dunning flow (Mahnlauf) has
been activated in the portal, and pending until then — a pending process is
promoted automatically at activation. A company-wide or debtor-level pause
parks it as paused. When a debt-collection case already covers the claim, the
process is created directly in inkasso.
Release is refused with 409 no_default_dunning_flow when no default dunning
flow is configured; configure one in the portal first. It is also refused for
an archived held invoice (invoice_archived) and for a settled invoice
(invoice_settled) — including one that was released and then paid, where
the settled state outranks the idempotent replay.
Payments and terminal states
POST /invoices/{uuid}/payments/ records a payment. A partial payment lowers
the reported balance while dunning continues over the full invoice
amount; once the reported total covers the amount, the invoice is marked paid
and dunning stops. The invoice exposes the read-only paid_amount (sum of
all reported payments) and overpaid (true when paid_amount exceeds
amount); over-payment is recorded, not refused, and balance never drops
below zero — reconcile a surplus in your own system.
Payment reporting is retry-safe: an identical repeat (same amount,
value_date and a non-empty reference, compared case-insensitively)
returns 200 with the existing payment instead of counting it twice. An
identical payment submitted without a reference is refused with
409 duplicate_payment, because a retry and a genuine second identical
instalment are otherwise indistinguishable — resubmit with a distinct
reference to record a real second payment.
POST /invoices/{uuid}/cancel/ and POST /invoices/{uuid}/write-off/ end the
claim and stop its dunning. Both are idempotent, and both answer 409 when the
invoice already reached a different terminal state or is in debt collection
(invoice_in_inkasso). dunning/pause and dunning/resume answer
409 invoice_paid, invoice_cancelled, or invoice_written_off on a settled
invoice, 409 not_released while the invoice is still held, and
409 invoice_in_inkasso once the claim is in debt collection. resume is
also refused with 409 company_paused while dunning is paused for the whole
company (by you in the portal or by paywise); lift that hold first. A
debtor-level hold does not block resume: the process resumes for that
invoice despite the hold. All five commands take no request body: null,
false, "", [], and {} are 400 unexpected_body as well.
Which conflict wins
Every mutating command checks the invoice in the same order and answers the first conflict it meets, so the code you receive is deterministic:not_api_invoice— the invoice belongs to a connected bookkeeping system.- The idempotent
200—cancelorwrite-offrepeated on the same terminal state,releaseon any released invoice. - The settled codes
invoice_paid,invoice_cancelled,invoice_written_off(releasereportsinvoice_settled; payment reporting is exempt frompaid). invoice_in_inkasso.- The correction window:
already_released(held-only commands:PATCH, document upload) ornot_released(released-only commands:pause,resume). invoice_archived(PATCH, document upload,release).- The command’s own codes —
payments_reported,duplicate_invoice_number,document_not_ready,no_default_dunning_flow,duplicate_payment,already_paused,invalid_status,not_paused,company_paused,complaint_requires_letter,stripe_connection_revoked.
A written-off invoice behaves like a cancelled one with
invoice_written_off
(write-off is the idempotent repeat instead of cancel). A bookkeeping
invoice answers 409 not_api_invoice in every cell. A 409 never changes the
invoice; updated is untouched.
Read the dunning state
GET /invoices/{uuid}/dunning/ returns the process state, the pause reason,
the current level, the next action date, the configured flow, and the history
of notices already sent with their fees. Once the process has ended — paid,
cancelled, written_off, completed, or inkasso — current_level and
next_action_date are null, and pause_reason and paused_at are cleared
even when the invoice was settled while paused. Use the
Mahnservice webhook events
to learn about invoice creation, payment, cancellation, write-off, dunning-level
advancement, and handover to collection, then fetch the current resource.
For reconciliation and changes without a dedicated event, poll this endpoint
or list invoices and use updated as a change marker. It advances on every
change you can observe through the API — a correction, a reported payment, a
state transition, a document reaching ready, rejected, or failed — and
never on internal retries or on a no-op PATCH. Invoice lists are offset
paginated and newest first; there is no updated_since filter on this API.
ordering sorts GET /mahnservice/v1/invoices/ by created, due_date,
amount, or invoice_number and GET /mahnservice/v1/debtors/ by created
or customer_number; a - prefix sorts descending, the default is
-created, and ties use a stable internal row key rather than the public
UUID. An unknown term is 400 on ordering with code invalid_parameter;
ordering on a detail or sub-resource route is 400 unknown_parameter.
Webhook events
Subscribe to invoice and dunning events through the company webhook system in the developer portal or POST/v2/webhooks/.
These subscriptions belong to your company. Partner integrations use
Partner-owned subscriptions.
Use the exact keys below in the subscription’s events list. See
Webhooks for the common event envelope,
signature verification, retries, and troubleshooting.
These events concern pre-collection invoices and dunning in the Mahnservice
API, including invoices imported from bookkeeping. Every event in this group
uses InvoiceWebhookEvent; its data contains company_id, invoice_id, and
invoice_url. Fetch invoice_url under /mahnservice/v1/invoices/{id}/ with a
credential authorized for the Mahnservice API.
invoice.created, the settlement events of a single invoice, and
dunning.level_advanced are recorded in the same transaction as the change
that raises them, so they are delivered even when the delivery worker is
briefly unavailable. A pre-existing collection case that flips an invoice to
inkasso records dunning.handed_to_collection in that same transaction.
For a new handover, the transfer transaction commits a durable handover record;
a relay records dunning.handed_to_collection under that record’s stable event
ID shortly afterward; its created_at is the relay time, and retrying the
relay does not create a second event.