Public Verification
Three calls let someone confirm that a document GoRoute issued is real, and read back what it says, without an API key. They exist so that a person holding a printed receipt or a scanned QR code can check it for themselves โ a customer, an auditor, an inspector โ none of whom has an account with you.
All paths below are relative to https://app.goroute.ai/peppol-api. None of the three takes
an Authorization header, an API key or a session. There is no organisation filter on any of
them.
| Call | What it answers |
|---|---|
GET /api/v1/public/verify/{transaction_id} | Does this transaction exist, and what does our record say about it? |
GET /api/v1/public/verify/{transaction_id}/download | Give me the original invoice XML. |
GET /api/v1/public/verify-receipt | Does a receipt with this reference and this total exist, and what was on it? |
Anyone who has a transaction ID can read that transaction's record and download the original invoice XML, with no key from GoRoute and no permission check of any kind. That is deliberate: it is what makes a QR code on a piece of paper checkable by the person holding the paper. The ID is a random UUID, so it cannot be guessed and the range cannot be walked.
But it means the ID carries the weight of a password for that one document. Do not put a transaction ID anywhere a password would not go:
- not in a URL you publish, print outside the receipt, or share in a message,
- not in a support ticket or a bug report,
- not in application logs you ship to a third party,
- not in a referrer-leaking link, an analytics event or an error tracker.
Losing one exposes one invoice โ the parties, the amount and the full XML โ to whoever picked it up. It does not expose anything else in your account.
The receipt check, verify-receipt, works differently and is not covered by that warning: it
needs two things off the same receipt and discloses nothing to someone holding neither.
Check a transactionโ
GET /api/v1/public/verify/{transaction_id}
transaction_id is the UUID GoRoute returned when the document was sent. A value that is not
a UUID is rejected with 422 before anything is looked up.
Returns the platform's record of the transaction, plus what the document itself says.
| Field | What it is |
|---|---|
id | The transaction ID you asked about. |
verified | Always true in a 200 response โ a transaction that does not exist is a 404, not a false. |
document_type | The kind of document, for example an invoice or a credit note. |
direction | Whether GoRoute sent or received it. |
status | Where the transaction got to. |
sender_peppol_id, receiver_peppol_id | The two Peppol addresses. |
message_id | The network message identifier. |
created_at, delivered_at | When the transaction was recorded, and when it was delivered. Either can be absent. |
document_number | The document's own number, as written on it. |
issue_date | The document's own issue date. |
currency | The document's currency. |
total_amount | The document's payable total, as a string. |
seller_name, buyer_name | The two party names off the document. |
document_storage_key | Internal. See the note below. |
Every field from document_type downwards can be absent or null. The document's own figures
are read out of the stored record rather than out of the XML, so a transaction whose record
never captured them returns nothing for them rather than failing.
document_storage_keyIt is GoRoute's internal reference to where the document sits in storage, and it should not be in an unauthenticated response at all. It is not an address, it is not stable, and nothing you can call accepts it. Do not read it, store it or display it. That it is exposed here is filed with engineering as issue #768.
When it refuses
| Status | error_code | Meaning |
|---|---|---|
| 404 | TRANSACTION_NOT_FOUND | No transaction with that ID. The message reads "Transaction not found or has expired". |
| 422 | โ | transaction_id is not a well-formed UUID. A standard validation body, not an error_code. |
Download the original documentโ
GET /api/v1/public/verify/{transaction_id}/download
GET /api/v1/public/verify/{transaction_id}/download?inline=true
Gets the original XML for a transaction. No authentication, exactly as above โ so everything in the warning at the top of this page applies here with more force, because this call hands over the document itself rather than a summary of it.
Two modes:
- Default. Answers a JSON object with
download_url, a temporary link to the document in object storage;expires_in, which is 300 (five minutes); andcontent_type, which isapplication/xml. Fetch it promptly โ after five minutes the link stops working and you must call this endpoint again. inline=true. Answers the XML itself, asapplication/xml, withCache-Control: no-store. Use this from a browser page. A temporary storage link cannot always be fetched from a web page in another origin, and serving the bytes from the API avoids that problem entirely.
When it refuses
| Status | error_code | Meaning |
|---|---|---|
| 404 | TRANSACTION_NOT_FOUND | No transaction with that ID. |
| 404 | DOCUMENT_NOT_STORED | The transaction exists, but no document content was kept for it. |
| 503 | DOCUMENT_STORAGE_NOT_CONFIGURED | Document downloads are not available on that deployment at all. Not a fault in your request. |
| 500 | DOWNLOAD_FAILED | The temporary link could not be generated. |
| 422 | โ | transaction_id is not a well-formed UUID. |
Check a printed receiptโ
GET /api/v1/public/verify-receipt?reference={invoice reference}&total={gross total}
Checks a point-of-sale receipt against the two values printed on it. Both query parameters are required; omitting either is a 422 naming the one you left out.
| Parameter | What to pass |
|---|---|
reference | The invoice reference on the receipt. Case and surrounding spaces do not matter. |
total | The gross total charged, including VAT, as a decimal โ compared to three decimal places. |
An invoice reference is sequential: the one above yours and the one below yours both exist. If a bare reference were enough, anyone could count through the range and read other customers' receipts โ names, amounts and all. Asking for the total as well costs the person holding the receipt nothing, because it is printed right there, and makes counting through the range useless.
For the same reason there is one refusal for both "no such reference" and "right reference, wrong total". Telling them apart would confirm that a reference exists, which is the thing being prevented.
A match must also be a receipt a point-of-sale endpoint actually wrote, and its seller must genuinely belong to the organisation whose record it is. A document posted by some other tenant under a seller name and VAT number of its choosing cannot answer for your reference and total, and cannot shadow the real receipt either.
What comes back
| Field | What it is |
|---|---|
verified | true. A receipt that does not match is a 404. |
reference | The reference, normalised to upper case. |
total | The gross total, to three decimals. |
currency | The receipt's currency; OMR where the record does not say. |
net_amount, vat_amount | The receipt's net and VAT figures. |
seller | The seller's trading name as printed. |
seller_vat | The seller's VAT number. |
branch, branch_id | The station or branch that made the sale. |
teller | The pump or till the sale came from. |
service | What was sold. |
customer | The customer name, where the sale recorded one. |
issued_at | When the sale was recorded. |
status | The transaction's status. |
invoice_uuid | The Invoice UUID โ BTOM-002 in the Oman architecture, identifying the business document. |
seller_uuid | The Seller UUID โ BTOM-004, identifying the receipt. Read back out of the QR payload rather than from a separate field, so it is the same value the paper shows. |
qr_tlv | The tax QR payload stored for the sale. |
qr_image | That payload rendered as a QR image, as a data: URI. Absent if it could not be rendered. |
reporting, reporting_note | Where the sale stands with tax reporting, in words. |
Every field other than verified, reference and total can be absent or null, depending
on what the sale recorded.
When it refuses
| Status | error_code | Meaning |
|---|---|---|
| 404 | RECEIPT_NOT_VERIFIED | "No receipt matches that reference and total." Covers both a reference we do not have and a total that does not match. |
| 400 | INVALID_TOTAL | total is not a decimal amount. The message gives the expected shape. |
| 422 | โ | reference or total was not supplied. |
The QR code on an Oman receiptโ
An Oman point-of-sale sale comes back with two QR codes, described under
The two QR codes. The second of
them, qr.verify_image_data_uri, encodes qr.verify_url โ a link to a GoRoute web page with
the receipt's values in the part of the URL after the #.
Among those values are the invoice reference and the gross total: the same two things
verify-receipt matches on. What that web page does with them is a matter for the page, not
for this documentation โ this page describes the API, and does not claim that scanning the
printed QR code calls any of the three endpoints above.
If you are building your own "check this receipt" feature, use verify-receipt directly with
the reference and the total. You do not need the link or the QR image to do it.