Test Identifiers
GoRoute ships a sandbox: a small set of fixed test recipients that behave the same way every time you send to them. Use them to prove your integration handles success and failure correctly, without putting anything on the real Peppol network.
There are nine of them. Every one uses the Peppol scheme 9999 and an identifier that
starts with test-. Nothing else is a sandbox recipient.
Sandbox behaviour is switched off in production. If you send to a 9999:test-โฆ recipient
on a production environment, the send is not simulated โ it is refused with the error
code SANDBOX_NOT_ALLOWED. Check which environment you are on before you start, using the
health check below.
Before you start: check which environment you are onโ
Call the health check. It needs no API key.
curl -X GET https://app-test.goroute.ai/peppol-api/health
Response:
{
"status": "healthy",
"version": "1.0.0",
"environment": "test",
"timestamp": "2026-09-04T04:26:50.868013"
}
The sandbox works when environment is any one of sandbox, development, test or
staging. If it reports production, nothing on this page applies to that host.
The examples use https://app-test.goroute.ai/peppol-api, which is a working test host โ
its health check reports "environment": "test". It is not necessarily the base URL issued
to your account, and more than one test hostname is in use at GoRoute. Use the base URL you
were given, and confirm it with the health check above before relying on it.
The nine sandbox participantsโ
Send to these as the receiver. Each one always produces the same outcome.
Sends that succeedโ
| Peppol ID | Result of the send | Transaction status |
|---|---|---|
9999:test-success | Succeeds | delivered |
9999:test-success-slow | Succeeds | delivered |
9999:test-duplicate-event | Succeeds | delivered |
9999:test-out-of-order | Succeeds | delivered |
9999:test-retry-then-success | Succeeds | delivered |
All five behave identically today: the send returns success straight away and the
transaction is marked delivered. The last four names describe timing and event-ordering
scenarios โ a slow delivery, a repeated notification, notifications out of order, a failure
followed by a successful retry โ but the service does not currently produce any of those
sequences. Treat all five as "a send that works". See
What this page does not describe.
Sends that failโ
Each of these makes the send fail with a specific error code, so you can check your error handling reacts to the right one rather than treating every failure alike.
| Peppol ID | Error code | Error message | Transaction status |
|---|---|---|---|
9999:test-recipient-not-found | RECIPIENT_NOT_FOUND | Sandbox: Recipient not registered in Peppol network | failed |
9999:test-as4-failure | AS4_DELIVERY_FAILED | Sandbox: AS4 delivery failed - receiver rejected message | failed |
9999:test-validation-failure | VALIDATION_FAILED | Sandbox: Receiver rejected document - Schematron validation failed | failed |
9999:test-timeout | TIMEOUT | Sandbox: Request timed out waiting for receiver acknowledgment | retrying, then failed |
test-timeout behaves differently from the other three. TIMEOUT is one of the error
codes GoRoute treats as worth retrying, so the transaction moves to retrying and the send
is attempted again โ up to three retries. Each attempt hits the same sandbox recipient and
fails the same way, so the transaction ends as failed after the retries are exhausted.
This makes it the right identifier to use when you want to see your integration cope with a
transaction that changes status several times before settling.
Sending to a 9999:test-โฆ identifier that is not in these two tables is refused with
the error code UNKNOWN_SANDBOX_RECEIVER, and the message lists the ones that do exist.
What sends a document to the sandbox instead of the real networkโ
Two conditions decide it. Both must hold, or your document goes to the real Peppol network.
- The receiver scheme is
9999and the receiver identifier starts withtest-. - The environment is a test one โ
sandbox,development,testorstaging.
If the first condition holds and the second does not โ a sandbox recipient on production โ
the send is refused with SANDBOX_NOT_ALLOWED rather than quietly delivered. That is
deliberate, so nobody on production is ever handed a fake success.
GoRoute's in-country Oman service has no sandbox. It reports every document it exchanges
to the Oman Tax Authority, so it never sends to a receiver under the scheme 9999, and
nothing is reported for one. How you are told depends on the route:
POST /documentsand the other routes that take a finished document refuse it on the call, before anything is recorded: HTTP 422 with the error codeSANDBOX_RECEIVER_NOT_ALLOWED. Nothing is stored or queued.POST /invoicesand the POS counters check the buyer's Peppol ID first, and9999is not a Peppol scheme on this service: the answer isvalidation_failedwith the ruleEDEC_001, and nothing is sent. Sending a draft is refused by the same check.- A transaction that already exists (retried with
POST /transactions/{id}/retry, or queued before this rule) endsfailedwith the error codeSANDBOX_RECEIVER_NOT_ALLOWEDand is never sent.
Use a GoRoute test environment for sandbox testing.
There is a third, softer rule about API keys. Sandbox use is intended for test keys,
which start with pk_test_ or sk_test_. This is a recommendation, not a gate: a key
without that prefix is not blocked from the sandbox on a test environment. Use a test key
anyway, so that your test traffic is unmistakable.
The sandbox endpointsโ
Four read-only endpoints let you ask the service itself what the sandbox contains, rather
than taking this page's word for it. All four need an API key in the X-API-Key header.
Outside a test environment, GET /api/v1/sandbox/status still answers HTTP 200, with
"available": false; the other three return HTTP 403 with the error code
SANDBOX_NOT_AVAILABLE.
| Endpoint | What it returns | Permission required |
|---|---|---|
GET /api/v1/sandbox/status | Whether the sandbox is available here, the rules it applies, and how many participants exist | transactions:read |
GET /api/v1/sandbox/participants | Every participant, with its outcome and error code | transactions:read |
GET /api/v1/sandbox/participants/{identifier} | One participant in detail | transactions:read |
GET /api/v1/sandbox/metrics | Sandbox usage counters, held in memory and reset whenever the service restarts | settings:manage |
For the participant detail endpoint, pass the identifier without the 9999: prefix โ
for example test-success, not 9999:test-success. An identifier that does not exist
returns HTTP 404 with the error code SANDBOX_PARTICIPANT_NOT_FOUND, and the response
lists the names that do exist.
Check the sandbox is availableโ
curl -X GET https://app-test.goroute.ai/peppol-api/api/v1/sandbox/status \
-H "X-API-Key: YOUR_TEST_API_KEY"
List the participantsโ
curl -X GET https://app-test.goroute.ai/peppol-api/api/v1/sandbox/participants \
-H "X-API-Key: YOUR_TEST_API_KEY"
Look one upโ
curl -X GET https://app-test.goroute.ai/peppol-api/api/v1/sandbox/participants/test-timeout \
-H "X-API-Key: YOUR_TEST_API_KEY"
A suggested order to test inโ
- Check you are on a test environment with the health check above.
- Confirm the sandbox is switched on with
GET /api/v1/sandbox/status. - Send to
9999:test-successand confirm you record a delivered transaction. - Send to
9999:test-recipient-not-foundand confirm your code reads the error codeRECIPIENT_NOT_FOUND, rather than assuming every failure is the same. - Send to
9999:test-validation-failureand confirm a rejected document is distinguishable from an undeliverable one in your own records. - Send to
9999:test-timeoutand confirm nothing in your system hangs, and that a transaction which passes throughretryingbeforefaileddoes not produce duplicate records on your side.
Example: the receiver in a UBL invoiceโ
The sandbox identifier goes in the customer party's endpoint, with schemeID set to
9999:
<cac:AccountingCustomerParty>
<cac:Party>
<cbc:EndpointID schemeID="9999">test-success</cbc:EndpointID>
<cac:PartyName>
<cbc:Name>Sandbox Test Receiver</cbc:Name>
</cac:PartyName>
<cac:PartyLegalEntity>
<cbc:RegistrationName>Sandbox Test Receiver</cbc:RegistrationName>
</cac:PartyLegalEntity>
</cac:Party>
</cac:AccountingCustomerParty>
The rest of the invoice is an ordinary Peppol BIS Billing 3.0 document. See Send Invoice for a complete example.
What this page does not describeโ
- Webhook notifications. Each sandbox participant carries a timeline of webhook events in the service's source โ queued, sending, delivered, and so on. Nothing in the running service sends them. Rather than describe notifications you would wait for and never receive, they are left out. Test your webhook handling against real transaction events instead โ see Webhooks.
- Country-specific test identifiers. The sandbox simulates delivery; it does not apply any country's invoice rules. To check a document against a country's requirements, validate it โ see Validation.
Moving to productionโ
Before you go live:
- Replace every
9999:test-โฆreceiver with a real Peppol identifier - Switch to your production API key
- Confirm the health check on your production base URL reports
"environment": "production" - Register your own Peppol identifier โ see SMP Registration
- Point your webhook URLs at your production system
- Work through the Production Checklist