Skip to main content
Invoice and Payment are sub-resources on the order. They carry the commercial artifacts Novatrade24 uses to validate VAT applicability at compliance evaluation time.

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

Upsert key: (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.
Mismatches surface as 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

Upsert key: the shipment the payment is recorded on — one record per shipment. This call addresses the order, so it needs the order to have a single shipment; a second POST then corrects the first.

Upload proof of payment

Allowed types: 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, bankAmount or cashAmount missing, or a paymentDate that is not ISO 8601.
  • An amount that is negative, or not a whole number of minor units. 1850000.5 is refused, not rounded.
  • A currency other than EUR.
  • A sourceIban that is not a valid IBAN.
Nothing else refuses the write, including a missing 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 on PaymentResponse 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 409 rather than picking one — the same call answers 200 while only one half is paid. The 409 detail lists the uuids of the halves that carry a payment; read each at GET /orders/{uuid}/shipments/{shipmentId}/payment.
  • You are writing. POST and PATCH /orders/{uuid}/payment answer 409 for 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 at POST /orders/{uuid}/shipments/{shipmentId}/payment/documents.
The shipment-scoped operations take the same bodies, issue the same 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.amountMatchesInvoice is false.
  • Source IBAN inconsistent with the buyer’s record → modules.payment.ibanConsistent is false.
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.