Skip to main content

Embedded Error Handling

All Embedded Local SDKs return JSON responses with a status field. This page covers every error category for the embedded SDKs. For Cloud API HTTP errors, see Error Handling.

Response Structure

Successful transaction

{
"status": "ok",
"fiscal_id": "ES-AEAT-2026-00001",
"signature": "a1b2c3d4...",
"hash_chain": "prev:0000...;curr:a1b2...",
"queued_at": "2026-03-22T10:00:00Z"
}

Failed transaction

{
"status": "failed",
"fiscal_id": "",
"error_message": "missing required field: amount"
}

Init error

{
"status": "error",
"error_message": "invalid config: unknown country code 'XX'"
}

Init Errors

These are returned by init() / initWithConfig(). The engine is not usable until init succeeds.

ErrorCauseAction
missing required field: countryConfig has no country fieldAdd the ISO country code to your config
missing required field: tax_idConfig has no tax_id fieldAdd the merchant's VAT / tax ID
invalid config: unknown country code 'XX'Unsupported countryCheck supported countries
invalid config: parse error at line NMalformed YAML/JSONFix syntax in your config file
invalid UTF-8 in config pathFile path contains invalid bytesUse a valid UTF-8 path string
invalid UTF-8 in config JSONJSON string is not valid UTF-8Ensure your string encoding is UTF-8
token provisioning failed: HTTP 401Invalid API keyVerify your api_key or ZYNTEM_API_KEY env var
token provisioning failed: HTTP 403Key doesn't have permission for this countryCheck your account's country entitlements
token provisioning failed: connection refusedCan't reach api.zyntem.comCheck network. This is the only network call required at init

Recovery pattern:

import zyntem_fiscal
import sys

result = zyntem_fiscal.init_with_config({
"country": "ES",
"tax_id": "B12345678",
"environment": "sandbox",
})

if result["status"] == "error":
# Init errors are not retryable (except network errors)
if "connection refused" in result["error_message"]:
# Retry with backoff -- token provisioning needs network once
pass
else:
# Config error -- fix and restart
print(f"Fatal: {result['error_message']}", file=sys.stderr)
sys.exit(1)

Transaction Errors

These are returned by processTransaction(). The engine remains usable -- fix the input and retry.

ErrorCauseAction
missing required field: amountTransaction JSON missing a required fieldAdd the missing field
invalid amount: must be positiveNegative or zero amountFix the transaction amount
invalid currency: 'XYZ'Unsupported ISO 4217 currencyUse a supported currency code
invalid UTF-8 inputInput string is not valid UTF-8Ensure UTF-8 encoding
engine not initializedprocessTransaction() called before init()Call init() first

Recovery pattern:

result = zyntem_fiscal.process_transaction(tx_data)

if result["status"] == "ok":
save_fiscal_id(result["fiscal_id"])
elif result["status"] == "failed":
# Transaction errors are input validation -- don't retry the same input
log_error(result["error_message"])
notify_operator(tx_data, result["error_message"])

Queue Monitoring

The submission queue runs in the background via the forward_worker. It handles its own retries -- your application doesn't need to manage this. However, you can monitor it.

status = zyntem_fiscal.queue_status()
{
"pending": 3,
"processing": 1,
"completed": 142,
"failed": 0,
"dead": 0,
"total": 146,
"alerts": []
}
FieldMeaning
pendingRecords waiting to be submitted
processingRecords currently being submitted
completedSuccessfully submitted records
failedRecords that failed but still have retries remaining
deadRecords that exhausted all retry attempts (20 max)
alertsDeadline alerts for approaching submission windows

Queue Entry Lifecycle

pending → processing → completed
↘ failed (retry with exponential backoff)
↘ dead (after 20 attempts)

When dead count > 0

Dead records have exhausted all 20 retry attempts. This means a transaction was signed locally but never reached the tax authority -- a compliance risk requiring manual intervention.

status = zyntem_fiscal.queue_status()
if status["dead"] > 0:
# Alert your operations team immediately
alert(f"{status['dead']} fiscal records permanently failed submission")

Common causes:

  • Tax authority endpoint down for extended period
  • Client certificate expired (for authorities requiring mTLS)
  • Merchant's tax registration revoked or suspended

Retry Behavior

The queue uses exponential backoff:

Attempt 1:  2s
Attempt 2: 4s
Attempt 3: 8s
...
Attempt N: min(2 * 2^(N-1), 21600s) -- capped at 6 hours

After 20 attempts, the entry moves to dead status and stops retrying.

Deadline Alerts

The queue monitors country-specific submission deadlines and raises alerts at three thresholds:

Alert levelThresholdMeaning
warning_75pct75% of window elapsedSubmission deadline approaching
critical_90pct90% of window elapsedDeadline imminent
breached_100pct100% of window elapsedDeadline passed -- regulatory risk

Submission windows vary by country (see Going to Production).

Diagnostic Checklist

If transactions are failing:

  1. Check init succeeded -- did init() return {"status": "ok"}?
  2. Check queue status -- are dead or failed counts increasing? Are there deadline alerts?
  3. Enable debug logging -- set FISCALIZATION_LOG_LEVEL=debug or logging.level: debug in config
  4. Check network -- can the host reach the tax authority endpoint?
  5. Check certificates -- if the authority requires mTLS, are the certs valid and not expired?
  6. Check the API key -- sandbox keys (zyn_test_*) don't work in production mode