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.
| Error | Cause | Action |
|---|---|---|
missing required field: country | Config has no country field | Add the ISO country code to your config |
missing required field: tax_id | Config has no tax_id field | Add the merchant's VAT / tax ID |
invalid config: unknown country code 'XX' | Unsupported country | Check supported countries |
invalid config: parse error at line N | Malformed YAML/JSON | Fix syntax in your config file |
invalid UTF-8 in config path | File path contains invalid bytes | Use a valid UTF-8 path string |
invalid UTF-8 in config JSON | JSON string is not valid UTF-8 | Ensure your string encoding is UTF-8 |
token provisioning failed: HTTP 401 | Invalid API key | Verify your api_key or ZYNTEM_API_KEY env var |
token provisioning failed: HTTP 403 | Key doesn't have permission for this country | Check your account's country entitlements |
token provisioning failed: connection refused | Can't reach api.zyntem.com | Check 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.
| Error | Cause | Action |
|---|---|---|
missing required field: amount | Transaction JSON missing a required field | Add the missing field |
invalid amount: must be positive | Negative or zero amount | Fix the transaction amount |
invalid currency: 'XYZ' | Unsupported ISO 4217 currency | Use a supported currency code |
invalid UTF-8 input | Input string is not valid UTF-8 | Ensure UTF-8 encoding |
engine not initialized | processTransaction() 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": []
}
| Field | Meaning |
|---|---|
pending | Records waiting to be submitted |
processing | Records currently being submitted |
completed | Successfully submitted records |
failed | Records that failed but still have retries remaining |
dead | Records that exhausted all retry attempts (20 max) |
alerts | Deadline 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 level | Threshold | Meaning |
|---|---|---|
warning_75pct | 75% of window elapsed | Submission deadline approaching |
critical_90pct | 90% of window elapsed | Deadline imminent |
breached_100pct | 100% of window elapsed | Deadline passed -- regulatory risk |
Submission windows vary by country (see Going to Production).
Diagnostic Checklist
If transactions are failing:
- Check init succeeded -- did
init()return{"status": "ok"}? - Check queue status -- are
deadorfailedcounts increasing? Are there deadline alerts? - Enable debug logging -- set
FISCALIZATION_LOG_LEVEL=debugorlogging.level: debugin config - Check network -- can the host reach the tax authority endpoint?
- Check certificates -- if the authority requires mTLS, are the certs valid and not expired?
- Check the API key -- sandbox keys (
zyn_test_*) don't work in production mode