Skip to main content

Tax reporting status: TDD and MLS

Tax reporting status (TDD and MLS)โ€‹

Oman's CTC protocol produces two artefacts alongside the invoice itself: a Tax Data Document (TDD) addressed to the Oman Tax Authority, and one or more Message Level Status (MLS) responses exchanged between access points. Four endpoints expose them: one for the reporting status, one to fetch the TDD, and two to fetch an MLS โ€” which of the two you need depends on who wrote the MLS, and Fetching an MLS document is the rule. All four require the transactions:read permission.

Reporting status for one transactionโ€‹

GET /api/v1/transactions/{transaction_id}/reporting

Returns the tax-reporting state of a transaction in a single call.

FieldMeaning
transaction_idThe transaction queried.
clearance_statusOne of prepared_awaiting_authority, cleared, failed, rejected, reported, reported_via_substitute, awaiting_ota_go_live, held (a report held before any TDD was built for it, with its reason in reason_code and reason), or null when no TDD exists yet. failed is final; reporting_state says whether it is a report that cannot be filed. rejected: the tax authority rejected the report and GoRoute's corrected report is under way; not final. See When a report does not reach the tax authority and When the tax authority rejects a report.
clearance_reason_code, clearance_reasonWhy the report reads prepared_awaiting_authority, held or rejected: a stable code and one sentence you can show to a person, the same pair GET /transactions/{id} carries. null for any other clearance_status.
reporting_state, reason_code, reasonnull while nothing has gone wrong. error when the report, or a correction to it, cannot be filed; held while a report that did not reach the tax authority waits for reporting for your taxpayer to be switched back on, or while a report whose processing stopped waits for GoRoute (see When a report's processing stops). reason_code is a stable code such as C5_OM_TDD_TRANSPORT_FAILED, and reason is one sentence you can show to a person.
holdnull unless the report's processing stopped: its reason (awaiting_operator_decision or lookup_unavailable) and since (UTC). See When a report's processing stops.
ota_liveWhether this environment is configured to report to a live tax authority.
ota_targetThe participant identifier Tax Data Documents are addressed to, as scheme:id.
ota_target_is_testbedtrue when ota_target is the OTA test participant.
noteA plain-language explanation, or null. See below.
mls_statusThe MLS response code if one has arrived, otherwise sent if an MLS was dispatched, otherwise null.
tdd.transaction_idThe transaction holding the TDD, or null.
tdd.sentEnqueued for transmission โ€” not "arrived". Read together with tdd.held.
tdd.heldtrue when the TDD was built, validated and stored but deliberately not transmitted. A held TDD has reached nobody. Two things hold one: the environment not being configured to report to the tax authority, or โ€” where an enabled-reporter list is in use โ€” this taxpayer not being on it.
tdd.deliveredtrue cleared, false failed, null not yet reconciled.
tdd.authority_responseThe tax authority's answer to the TDD, or null until it answers: response_code (AP accepted, RE rejected), its description, received_at and mls_id.
tdd.redrivenull unless the TDD failed to reach the tax authority. Otherwise GoRoute's automatic re-sends of it: attempts, max_attempts, state (in_progress, held, error, reported, or answered when the authority answered it after all), next_attempt_at and last_error_code.
tdd.scopeScope of a substitute report, where one was used.
tdd.document_urlRelative URL of the stored TDD, or null when there is no TDD.
mls.transaction_idThe MLS we sent, kept for backward compatibility.
mls.response_codeResponse code carried by the MLS.
mls.document_urlRelative URL of the MLS we sent, or null.
mls.legs[]One entry per leg that exists โ€” leg is sent or received, each with its own transaction_id and document_url. A received leg also carries response_code and received_at. The document_url on a received leg does not work: it points at the core MLS call, which cannot return that leg. Read the leg's presence and response_code from here, but fetch it with the Oman call โ€” see Fetching an MLS document. The Tax Authority's MLS is not listed here at all; tdd.authority_response is where it appears.
substitute.usedtrue when a substitute participant was reported to.
substitute.participantThe substitute participant identifier.
substitute.reasonWhy a substitute was used.
sla_breachedWhether the reporting SLA for this transaction was breached.
Treat reason_code as an open set

The codes named on this page are the ones worth writing code for, but the set grows as reporting gains new cases, and your own service may be a release behind or ahead of this page. So branch on the codes you have deliberately handled, and for anything else show the reason sentence to the person instead of a fallback of your own. reason is written to be read by a human being and is never an internal error message, so it is always safe to show. A switch with no default is the one thing to avoid here: a code you have not seen should produce a useful message, not silence.

tdd.sent does not mean the tax authority received it

sent means enqueued for transmission. A held TDD is enqueued and then deliberately not transmitted, so tdd.sent and tdd.held are both true at once. Always read the two together: if tdd.held is true, nothing has reached the tax authority. clearance_status already applies that precedence for you โ€” prefer it over inferring the state from tdd.sent.

Why you may see an explanatory noteโ€‹

When clearance_status is anything other than reported or rejected, reporting_state is not error, the report's processing has not stopped (hold is null; while it has, note is the sentence in reason), and this environment is pointed at the OTA test participant or is not configured as live, the response carries a note explaining that in prose. This is the expected state today rather than a fault: the invoice itself is delivered to the buyer over Peppol normally, the TDD is still generated and stored against the transaction, and the original issue time is preserved so the reporting obligation can be evidenced later. When the note is absent it is null.

GoRoute publishes no date for OTA production reporting. Use ota_live and ota_target_is_testbed on this endpoint to see what your own environment is pointed at, rather than assuming.

A held report with no note at all means something specific. If clearance_status is prepared_awaiting_authority while ota_live is true and ota_target_is_testbed is false, the response carries no explanatory note, because neither condition above applies. That combination means the environment is configured to report to the tax authority, but this taxpayer is not one of those enabled for transmission. Where an enabled-reporter list is in use, only the taxpayers named on it have their Tax Data Documents dispatched; everyone else's are held exactly as if reporting were switched off. The list is a deployment setting and no API call exposes it, so there is nothing you can read to confirm it โ€” ask GoRoute whether your taxpayer identifier is enabled. clearance_status remains the field to read: it applies the held-first precedence for you, whichever of the two reasons applies.

Fetching the Tax Data Documentโ€‹

GET /api/v1/transactions/{transaction_id}/tdd

Returns the stored TDD as UBL XML with content type application/xml. The document is returned exactly as it was stored โ€” nothing is reformatted or re-serialised. Use the transaction identifier of the invoice, not of the TDD itself; the endpoint resolves the TDD from the invoice for you.

There is no parameter to choose which TDD you get. A transaction can have up to three: the TDD GoRoute produced itself, a TDD produced by the sending access point (C2), and a substitute TDD. The endpoint returns the first of those three that exists, in that order, and there is no equivalent of the leg parameter on the MLS call. To see which one you are about to receive, call the reporting endpoint first: tdd.transaction_id applies the same order, so it identifies the document this call will return, and substitute.used tells you whether a substitute was reported to.

Errors

Statuserror_codeWhen
404TRANSACTION_NOT_FOUNDNo such transaction in your organisation.
404TDD_NOT_AVAILABLEThe transaction exists but no TDD has been produced for it.
404DOCUMENT_NOT_STOREDA TDD exists but its XML is not retrievable from storage.
document_url is not a complete path โ€” add /api/v1

The document_url values in the reporting response are built without the /api/v1 prefix: tdd.document_url comes back as /transactions/{transaction_id}/tdd, and the MLS URLs are built the same way. Joining your API base URL to that value as-is produces a 404. Either prepend /api/v1, or build the address from the request line above rather than from the response field. For the MLS there is a second reason to build your own address: the received leg's document_url names a call that cannot return it. See Fetching an MLS document.

Being able to download a TDD does not mean the Tax Authority received it. A held TDD is built, validated, stored and fetchable through this endpoint while having reached nobody โ€” check tdd.held and clearance_status on the reporting endpoint before treating a successful download as evidence of reporting.

TDD (Tax Data Document)โ€‹

Oman's continuous transaction controls require a Tax Data Document (TDD) to be reported to the Oman Tax Authority. GoRoute builds and files it for you:

  • Root element: pxs:TaxData (namespace urn:peppol:schema:om-taxdata:1.0)
  • Receiving party: the Oman Tax Authority's Peppol participant (scheme 0242). GoRoute fills it in; you never set it.
  • Validated against TDD-OM-peppol-om-tdd.xslt before submission
  • Document type codes (pxs:DocumentTypeCode): S Submit, R Resubmit, D Disregard (OpenPeppol code list)

This page does not give a reporting deadline, because we could not trace one to a Tax Authority or OpenPeppol publication.