Skip to main content

Sandbox Modes

Test mode (zyn_test_* keys) supports two routing modes for fiscalization. Emulator is the default — Zyntem synthesises a stubbed authority response so you can exercise the full request shape without uploading a certificate or making a network call to the regulator. Authority routes to the real country sandbox endpoint and requires a sandbox cert on file.

You select the mode per request with the X-Zyntem-Sandbox header. Both modes are test-key only — live keys reject either header with 400 Bad Request.

Emulator vs authority

ModeHeaderCert requiredNetwork round-tripUse it for
Emulator (default)X-Zyntem-Sandbox: emulator (or omit)NoNo — fully syntheticWiring up requests, integration tests, error-path coverage
AuthorityX-Zyntem-Sandbox: authorityYes — POST /v1/certificates firstYes — real sandbox endpointEnd-to-end conformance against the regulator's sandbox

The resolved mode is echoed back to you in two places:

  • The X-Zyntem-Sandbox response header on every test-key request.
  • The sandbox_mode field on the transaction response body ("emulator", "authority", or null for live keys).

This is a one-bit difference at request time but a large one operationally — emulator mode lets ISVs onboard, run their CI suite, and drive every error path without ever talking to the tax authority. Switch to authority mode only when you specifically need to verify the regulator's own sandbox accepts your payloads.

Choosing a mode

Default to emulator. It's the right choice for ~90% of test-mode traffic:

  • New integrations: get a 201 Created for a transaction before you've even opened the cert-onboarding portal.
  • CI / automated tests: deterministic, network-free, no shared sandbox state across builds.
  • Error-path testing: pair with X-Zyntem-Test-Scenario to drive every fault class on demand (see below).
  • Demos and onboarding flows: show end-to-end behaviour without provisioning a regulator account.

Opt into authority when you need ground-truth from the regulator:

  • Pre-launch conformance checks before flipping a country to zyn_live_.
  • Investigating a discrepancy between Zyntem's emulator and the regulator's actual response shape.
  • Reproducing an issue that was only seen in production (the authority sandbox is the closest available proxy).

Authority mode requires that the location has a sandbox certificate on file. If it doesn't, the 400 response includes a discovery hint pointing back at emulator mode plus POST /v1/certificates:

Location "Madrid HQ" requires certificate to reach the ES sandbox. To skip
the round-trip with a stubbed response, omit the X-Zyntem-Sandbox header
(defaults to emulator) or set it to emulator. To upload a sandbox cert,
POST /v1/certificates.

Triggering specific errors with X-Zyntem-Test-Scenario

The emulator's default outcome is success. To exercise an error path, send X-Zyntem-Test-Scenario with a value from the catalog below. Each scenario maps to a generic error envelope that the country adapter translates into its native shape (TBAI fault codes, SDI error tuples, KSeF processingCode, etc.) — your code sees the same stable scenario name across every country.

ScenarioHTTPRetry-AfterWhat it represents
success (default)200Happy path. Synthetic authority acceptance.
cert_revoked400Signing certificate revoked by the issuing CA.
cert_expired400Signing certificate past its notAfter.
throttled42960sAuthority returned 429 Too Many Requests.
malformed_response502Authority returned a payload that failed to parse.
timeout5045sAuthority did not respond before the runtime timeout.
invalid_tax_id400Tax id failed authority-side format / registry validation.
network_down50230sDNS / TCP connection to the authority could not be established.
authority_unavailable503120sAuthority is in maintenance / outage.
duplicate_transaction409Indistinguishable transaction already accepted by the authority.

Examples

Verify your client retries on a 429:

curl -X POST https://api.zyntem.dev/v1/transactions \
-H "Authorization: Bearer zyn_test_abc123def456..." \
-H "Content-Type: application/json" \
-H "X-Zyntem-Test-Scenario: throttled" \
-H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000" \
-d '{ ... }'

The response carries Retry-After: 60 and an X-Zyntem-Sandbox: emulator echo header; the underlying error envelope arrives in the country adapter's native shape.

Drive a cert-expiry alert:

curl -X POST https://api.zyntem.dev/v1/transactions \
-H "Authorization: Bearer zyn_test_abc123def456..." \
-H "X-Zyntem-Test-Scenario: cert_expired" \
-d '{ ... }'

Confirm idempotent replay handling:

curl -X POST https://api.zyntem.dev/v1/transactions \
-H "Authorization: Bearer zyn_test_abc123def456..." \
-H "X-Zyntem-Test-Scenario: duplicate_transaction" \
-d '{ ... }'

Combine with authority mode — the scenario header still picks the emulator's outcome shape, but the regulator round-trip (and cert validation) happens first:

curl -X POST https://api.zyntem.dev/v1/transactions \
-H "Authorization: Bearer zyn_test_abc123def456..." \
-H "X-Zyntem-Sandbox: authority" \
-H "X-Zyntem-Test-Scenario: timeout" \
-d '{ ... }'

Unknown scenario values are rejected with 400 Bad Request and the response body lists every accepted value, so you can self-correct without consulting docs.

What's deterministic vs synthetic

Emulator responses are deterministic and idempotent: the same Idempotency-Key plus the same scenario header always yields the same envelope. Fiscal IDs and timestamps are synthesised but stable for the lifetime of the transaction record, so you can assert against them in tests.

What you do not get from the emulator: a real regulator-signed receipt, a working QR code that resolves on the regulator's verifier portal, or coverage of regulator-specific quirks that aren't in the catalog. For any of those, switch to authority mode.

Going to live mode

When you're ready to flip a country to production, treat the test → live transition as a discrete promotion step rather than a key swap.

Pre-promotion checklist:

  1. Acceptance in emulator — every transaction shape your application emits (sales, refunds, multi-line, multi-tax) returns 201 Created with sandbox_mode: "emulator". Run this against your CI suite.
  2. Error-path coverage — your client gracefully handles every catalog scenario above. At minimum, verify throttled (retries), authority_unavailable (back-off), cert_expired (alerts your ops team), and duplicate_transaction (does not double-charge).
  3. Sandbox cert uploadedPOST /v1/certificates succeeded for each location that will go live. The cert must be the regulator's sandbox cert, not the production one yet.
  4. Authority-mode dry run — repeat step 1 with X-Zyntem-Sandbox: authority. Compare the transaction body against the emulator response — fiscal IDs and timestamps will differ, but the shape and status should match.
  5. Production cert uploaded — repeat the cert upload with the live cert. The cert is bound to the location, not the API key, so this is a per-location action.
  6. Switch the API key — replace zyn_test_* with zyn_live_* in your production deployment. Do not send X-Zyntem-Sandbox or X-Zyntem-Test-Scenario on live keys; both will return 400 Bad Request.
  7. Watch the environment field — the first live transactions should report "environment": "live" and "sandbox_mode": null. Alert on any drift.

See the Authentication guide for key management and Sandbox routing for how the test/live key split interacts with server-side FISCALIZATION_LIVE_ENABLED.

Live keys: no test-mode headers

Live keys (zyn_live_*) reject either header with 400 Bad Request:

{
"error": "Test-mode headers only valid on test API keys"
}

The sandbox_mode field on the transaction response is null and the X-Zyntem-Sandbox response header is absent for any live-key request — there's nothing to echo because there's no mode to resolve.

This is enforced server-side; you cannot accidentally route live traffic through the emulator by adding the header.