Invoice
One VAT security deposit invoice per order (type: PRELIMINARY).
This deposit secures the VAT and is not an advance payment towards the vehicle price. The final invoice goes to the same
operation with type: FINAL once the amounts are settled — there is no
separate endpoint for it.
Submit invoice data
(orderUuid, type). Re-submitting with the same body is
idempotent; different body returns 409, use PATCH to update.
amount follows the same rules as every other Money in this API, and a body
that breaks them returns 422 and stores nothing: negative, not a whole number
of minor units (1850000.5 is refused, not rounded), or a currency other than
EUR. The same holds for totalAmount and each VIN’s purchaseAmount on
order creation.
Upload the invoice PDF
Attached as a typed document:Validation performed
At compliance time, NT24 checks:- Invoice total matches order total (modulo currency).
- VAT structure matches the invoice type claim (
vatIncluded,marginScheme). - Buyer name on the invoice matches the buyer record.
- PDF is attached.
blockers[] in the compliance rollup.
Payment
One payment record per shipment, and an ordinary order has one shipment — so one payment per order, unless NT24 has split it. The record carries amount, date, IBAN, and optionally the breakdown between bank transfer and cash.Submit payment data
POST then corrects the first.
Upload proof of payment
Either of the first two counts as the proof of payment the order needs; send
whichever matches the document you hold. The third answers the cash rule below
and does not stand in for the transfer itself.
Pickup readiness
There is nothing to call.readyForPickup is derived on every read, so an order
becomes ready by satisfying the rule rather than by being marked — GET /payment
and the order’s status both report it.
Recording the payment is necessary but not sufficient: the documents evidencing
it have to be on file too. What the response
reports states the rule in full.
Earlier drafts of this specification published a
POST /payment/ready-for-pickup operation. It never had a handler and was
removed in 1.2.0-draft. If you generated a client against 1.1.0-draft or
earlier, the method for it is dead code; delete it.
Buyer VAT revalidation does not depend on readiness. Recording a payment runs a
VIES check on the buyer, and from then until pickup is recorded NT24 re-checks
the buyer’s VAT number every day (see VIES revalidation).
Outside that window NT24 revalidates on its own schedule — weekly for trade
partners with recent trading activity, monthly for dormant ones. If a
revalidation fails, kyc.vies_check_failed
fires and the rollup’s buyer module reports viesResult: INVALID. It adds no
blocker and does not move complianceStatus; there is no failed compliance
status for it to move to.
What the write checks
POST /payment refuses a body only over its own fields. These return 422
and store nothing:
paymentDate,bankAmountorcashAmountmissing, or apaymentDatethat is not ISO 8601.- An amount that is negative, or not a whole number of minor units.
1850000.5is refused, not rounded. - A
currencyother thanEUR. - A
sourceIbanthat is not a valid IBAN.
sourceAccountComment.
An irregular payment is still a payment that happened, and you have to be able
to record it.
What the response reports
Three fields onPaymentResponse annotate the payment. None of them gate it.
The IBAN comparison is exact. NT24 normalises case and separators, checks the
IBAN’s own checksum, then compares for equality against the buyer’s registered
accounts. An IBAN one digit off a registered one does not match, because that
near miss is the discrepancy the check exists to catch. Registered accounts come
from the buyer’s KYC record and this API has no endpoint that adds one, so a
payment from an account NT24 has no record of returns
201 with
ibanConsistent: false.
An account NT24 has no record of is something to document, not a fault in
the order: the payment is filed and the reason it came from elsewhere is
recorded beside it. The mismatch itself raises no blocker.
The reason is required, though, and at one point only: closing the order.
Send it in sourceAccountComment, on the same POST or on a later PATCH.
A payment with ibanConsistent: false and no comment is stored with 201,
but the order cannot be completed until a comment is on file, and until then
the compliance rollup carries a PAYMENT_REGISTERED_COMMENT_REQUIRED blocker
in the payment module. This is the
rule the platform applies to its own users, who can also save the answer
before typing the reason, and a comment NT24 staff enter on the platform is
the same comment, so either satisfies it. GET /payment returns the comment
on file. Only NT24 staff can close such an order without one, as an override
for cases such as a trade partner who can no longer be reached; the archival
PDF then says the order was closed that way.
amountMatchesInvoice compares against the invoice submitted for this order,
not against the order’s totalAmount. Those two figures can legitimately differ
and no check relates them. Until an invoice amount is on file the field is
absent rather than false, which would claim a mismatch against a number nobody
submitted.
readyForPickup turns true once paymentDate is set and an active proof of
payment — a PAYMENT_BANK_TRANSFER_SCREENSHOT or a PAYMENT_CONFIRMATION,
either one — is filed against the order. Cash of €10,000 or more also needs an
active PAYMENT_SOURCE_OF_FUNDS_DECLARATION. It is derived on every read, so
it can turn back to false: raise cashAmount past that threshold and the order
waits for the declaration. On an order NT24 has split across several shipments,
every shipment has to answer — half an order with no papers is not collectable
whichever half you are reading.
It reports payment, not permission. The order’s status reaches
READY_FOR_PICKUP on the same rule, so the two never disagree.
When NT24 has split the order
An order normally has one shipment, and/orders/{uuid}/payment is all you
need. NT24 staff sometimes split an order into two shipments, and then the
payment belongs to one half rather than to the order. The order tells you which
halves exist: OrderResponse.shipmentIds lists them, oldest first, one entry for
an ordinary order.
Reading /orders/{uuid}/payment still works, and it reports the payment
wherever it sits — you do not have to know the order was split to read it. Two
cases need the shipment:
- Both halves are paid. The order has no single payment, so the read answers
409rather than picking one — the same call answers200while only one half is paid. The409detail lists the uuids of the halves that carry a payment; read each atGET /orders/{uuid}/shipments/{shipmentId}/payment. - You are writing.
POSTandPATCH /orders/{uuid}/paymentanswer409for a split order, because the order alone does not say which half you mean. Name the shipment instead:POST/PATCH /orders/{uuid}/shipments/{shipmentId}/payment, and file the proof atPOST /orders/{uuid}/shipments/{shipmentId}/payment/documents.
ETag
semantics and apply the same gates as the order-scoped ones. They are served for
a single-shipment order too, so you can address the shipment always and keep one
code path. readyForPickup is the exception that stays order-wide, for the
reason above.
Cross-module validation
The write path annotates. The compliance rollup is where the same facts are surfaced across modules — as fields on the module, not as blockers:- Invoice amount ≠ payment amount →
modules.payment.amountMatchesInvoiceisfalse. - Source IBAN inconsistent with the buyer’s record →
modules.payment.ibanConsistentisfalse.
blockers[] is a narrower thing: it is what the platform still needs before
the order can close, so every entry names something absent rather than two
recorded values that disagree. Its code is a closed enum —
DOCUMENT, KYC_VALIDATION, CONTACT_VERIFICATION, VIN_CONFIRMATION,
PAYMENT_REGISTERED_COMMENT_REQUIRED — and a mismatch is none of them. The
last one is the missing comment on an inconsistent IBAN, not the
inconsistency. Read the booleans above for mismatches and
blockers[] for what is missing.
Resolution either way is to upload corrected data / document or submit a reason
via a PATCH where the field supports it.
Next
Transport
Pickup and delivery milestones.
Compliance rollup
One call for every module state, plus what is still missing.