Fetching an MLS: the three legs
Fetching an MLS document
There are two calls, and which one you need depends on who wrote the MLS you want. Get this the wrong way round and you will be told there is no answer when there is one, so the rule comes first:
| The MLS you want | Who wrote it | The call to use |
|---|---|---|
| The one GoRoute sent as C3, acknowledging a document that arrived for you | GoRoute, on your behalf | GET /api/v1/transactions/{transaction_id}/mls?leg=sent |
| The buyer's accept or reject of an invoice you sent | The buyer's access point (C2) | GET /api/v1/oman/transactions/{transaction_id}/mls?leg=received |
| The Oman Tax Authority's answer on your invoice's Tax Data Document | The Tax Authority (C5) | GET /api/v1/oman/transactions/{transaction_id}/mls?leg=authority |
GET /api/v1/transactions/{transaction_id}/mls?leg=received looks the MLS up inside your
own organisation. Both of the answers that come back to you — the buyer's and the Tax
Authority's — are addressed to GoRoute's own service-provider identity rather than to yours,
so they are filed under GoRoute's organisation and that lookup finds nothing. The call
answers 404 DOCUMENT_NOT_STORED, and it does so whether or not the buyer has answered.
A 404 from that call is therefore not evidence that nobody has answered. If you have
been treating it that way, you may have missed rejections. Use the Oman call below for the
received and authority legs. leg=sent on the core call is unaffected and still correct.
The core call — the MLS GoRoute sent
GET /api/v1/transactions/{transaction_id}/mls?leg=sent
Returns the MLS as unaltered UBL ApplicationResponse XML, with content type
application/xml. Nothing is reformatted or re-serialised. Permission:
transactions:read.
| Parameter | Values | Behaviour |
|---|---|---|
leg | sent | The ApplicationResponse GoRoute issued as C3 to the sending access point (C2). This is the leg this call is for. |
leg | received | Accepted by the call, but see the warning above: for an Omani invoice it answers 404 DOCUMENT_NOT_STORED. Use the Oman call instead. |
leg | omitted | The sent leg if one exists, otherwise it falls back to the received leg — and so to the same 404. Name the leg rather than omitting it. |
Errors
| Status | error_code | When |
|---|---|---|
| 404 | TRANSACTION_NOT_FOUND | No such transaction in your organisation. |
| 404 | MLS_NOT_AVAILABLE | The transaction exists but no MLS leg of the kind you asked for has been recorded on it at all. |
| 404 | DOCUMENT_NOT_STORED | The leg is recorded on the transaction but the call cannot read it. For leg=received on an Omani invoice this is the normal answer, for the reason in the warning above — not a sign of missing storage. |
The Oman call — the buyer's answer and the Tax Authority's
GET /api/v1/oman/transactions/{transaction_id}/mls?leg=received
This reads the MLS across organisations, after proving that the document it returns really does answer the invoice you named. Details:
| Point | Detail |
|---|---|
leg | Required. received — the buyer's access point's MLS on your invoice. authority — the Tax Authority's MLS on your invoice's Tax Data Document. There is no default and no sent value here. |
| Permission | transactions:read, the same as the core call. |
| Response | 200 with the stored UBL ApplicationResponse, unaltered, as application/xml. |
| Transaction | Your own invoice, read in your own organisation. A transaction belonging to someone else is a 404, not a 403. |
Errors
| Status | error_code | When |
|---|---|---|
| 404 | TRANSACTION_NOT_FOUND | No such transaction in your organisation. |
| 404 | MLS_NOT_AVAILABLE | No MLS of that leg has been received yet. This is also the answer if the stored MLS turns out not to answer the invoice you named, so that a mismatch can never leak another document. |
| 404 | DOCUMENT_NOT_STORED | The MLS was received but its XML is not retrievable from storage. |
The authority leg is new capability rather than a replacement: the core call has no
selector for the Tax Authority's MLS at all, so before this call existed there was no way to
download it.
It is part of the Oman country pack, and a pack is mounted only on a service configured to
run it. It is live on the test service; it is not mounted on app.goroute.ai today, even
though that service runs the same build. You can tell the two apart in one request, with no
API key and any transaction id:
- 401 — the route exists and is asking you to authenticate. It is available.
- 404 — the route is not mounted on that service.
Compare it against /api/v1/transactions/{id}/mls, which answers 401 everywhere: if the
Oman path gives 404 while the core path gives 401, the pack is off rather than your request
being wrong.
Fetching both, in order
Ask the reporting endpoint which legs exist, then fetch each from the call that can return
it. Note that mls.legs[] publishes a document_url for the received leg that points at the
core call — the address that cannot serve it. Build the address from the request lines above
rather than from that field.
import requests
status = requests.get(
f"{BASE_URL}/api/v1/transactions/{transaction_id}/reporting",
headers={"X-API-Key": API_KEY},
).json()
print(status["clearance_status"], status["ota_target"])
if status["note"]:
print(status["note"])
legs = {leg["leg"] for leg in status["mls"]["legs"]}
# The MLS we sent: the core call.
if "sent" in legs:
sent_xml = requests.get(
f"{BASE_URL}/api/v1/transactions/{transaction_id}/mls",
params={"leg": "sent"},
headers={"X-API-Key": API_KEY},
).text
# The buyer's answer: the Oman call. The core call answers 404 for this leg.
if "received" in legs:
received_xml = requests.get(
f"{BASE_URL}/api/v1/oman/transactions/{transaction_id}/mls",
params={"leg": "received"},
headers={"X-API-Key": API_KEY},
).text
# The Tax Authority's answer on the TDD. It is not listed in mls.legs[];
# tdd.authority_response tells you whether it has arrived.
if status["tdd"]["authority_response"]:
authority_xml = requests.get(
f"{BASE_URL}/api/v1/oman/transactions/{transaction_id}/mls",
params={"leg": "authority"},
headers={"X-API-Key": API_KEY},
).text
If you would rather not poll at all, the buyer's status message also reaches the invoice's owner as a webhook event — see Status events for invoices you sent.