Skip to main content

Invoice Drafts

A draft is an invoice held in GoRoute that has not been sent. You create it, read it, change it and validate it as often as you like, and nothing leaves GoRoute until you explicitly send it. Drafts are what you build an approval step out of: a document can sit in front of a human for as long as that takes, and the Peppol network never sees it.

Every draft belongs to one organisation. A draft you did not create, in an organisation you are not authenticated against, does not exist as far as the API is concerned โ€” the read answers 404, not 403.

What a draft is for

Nothing in GoRoute puts an invoice into an approval queue by itself, and no send path consults your approval rules. If you need a sign-off before a document goes out, hold it as a draft and send it after the sign-off. See an approval step you can build today.

Reading a draftโ€‹

GET /api/v1/drafts/{draft_id}

Returns the draft's stored invoice data together with the validation state recorded the last time it was validated:

FieldWhat it is
idThe draft's identifier
nameThe name you gave the draft, if any
invoice_numberExtracted from the invoice data for display
statusWhere the draft is in its life โ€” see below
invoice_dataThe whole invoice, in the same shape you would send to POST /api/v1/invoices
is_validThe result of the last validation, not a fresh one
validation_errorsThe errors from that same last validation
validation_scoreA completeness score from 0 to 100, from that same validation
transaction_idThe transaction the draft became, once it has been sent
error_messageWhy the last send or import failed, when one did
buyer_name, buyer_peppol_id, currency, total_amount, issue_date, due_dateExtracted from the invoice data for lists and search
created_at, updated_atTimestamps

is_valid, validation_errors and validation_score are stored, not computed on read. They tell you what validation said when it last ran. If you have changed the draft since, they are stale โ€” validate again before you trust them.

A draft that is not yours, or an identifier that does not exist, is refused with 404 "Draft not found". Both reads are scoped to the organisation your credential belongs to.

Validating a draft โ€” this changes the draftโ€‹

POST /api/v1/drafts/{draft_id}/validate

The answer is the validation result:

{
"is_valid": false,
"score": 85,
"errors": [
{
"code": "BUS_014",
"message": "Net amount is required in totals",
"field": "totals.net_amount"
}
],
"warnings": []
}

Each entry in errors and warnings carries a code, a message and the field it is about. score is a completeness score from 0 to 100. It starts at 100 and loses 15 points for every error and 3 for every warning, never going below 0 โ€” which is why the single error above scores 85. A draft whose data cannot be parsed at all scores 0.

Validating a draft writes to it by default

This call has a persist query parameter and it defaults to true. On the default, the result is written back onto the draft โ€” is_valid, validation_errors and validation_score are replaced, and the draft's status moves:

  • A draft that passes becomes ready.
  • A draft that fails becomes draft again โ€” unless it is in the exception state, which is left alone, so an imported invoice stays in the exception list until it genuinely passes or is sent.

Send persist=false for a read-only preview. Use that whenever you are only inspecting a draft, for example when opening a correction screen: with the default, merely looking at an imported invoice can move it out of the exception list.

Before validating, the call prepares the stored data: it fills the seller in from your saved company settings if the draft has none, and normalises the payload so it parses into the canonical invoice model. That is why an imported draft usually comes back with business-rule errors you can act on, naming the field, rather than one unhelpful parse error. If the data cannot be parsed at all you get a single error with code PARSE_ERROR.

See Validation for what the rules are and what the error codes mean.

Draft status valuesโ€‹

The values you will see in status are draft, ready, sending, sent, error and exception. This page describes only the moves it has traced: validation setting ready or draft, and leaving exception alone. Do not treat the list as a state machine โ€” read the status you are given rather than predicting it.

The rest of the drafts APIโ€‹

These calls exist and are listed in the API reference with their parameters and response shapes.

CallWhat it does
POST /api/v1/draftsCreate a draft โ€” empty, with invoice data, or from a saved template
GET /api/v1/draftsList drafts, paginated
GET /api/v1/drafts/recentThe most recently updated drafts
GET /api/v1/drafts/periodsYear and month rollup of invoices by issue date
PUT /api/v1/drafts/{draft_id}Replace the draft's invoice data
PATCH /api/v1/drafts/{draft_id}Change the draft's name
DELETE /api/v1/drafts/{draft_id}Delete one draft
DELETE /api/v1/drafts/bulkDelete several drafts in one call
POST /api/v1/drafts/{draft_id}/duplicateCopy a draft into a new one
GET /api/v1/drafts/{draft_id}/pdfThe draft rendered as a PDF
GET /api/v1/drafts/{draft_id}/ublThe draft rendered as UBL 2.1 XML
GET /api/v1/drafts/{draft_id}/auditWho changed which field, and when
GET /api/v1/drafts/{draft_id}/audit.csvThe same audit trail as a CSV download
POST /api/v1/drafts/{draft_id}/sendSend the draft over Peppol
POST /api/v1/drafts/{draft_id}/send-via-gorouteFirst email send of the draft โ€” see Sender domains
POST /api/v1/drafts/{draft_id}/resendSend the invoice email again
GET /api/v1/drafts/{draft_id}/resendsThe log of resend attempts
POST /api/v1/drafts/cleanupRemove old drafts