Skip to main content

Documentation Index

Fetch the complete documentation index at: https://dev.openfiskal.com/llms.txt

Use this file to discover all available pages before exploring further.

Platform guarantees

OpenFiskal publishes these current platform targets for the fiscalization API:
AreaGuidance
Availability target99.9% monthly for production
Typical synchronous latencyp50 < 300 ms, p95 < 1200 ms for standard operation mutations
Client timeout guidance10s request timeout, 30s total retry budget for synchronous calls
Rate limiting500 requests/second per API key
429 behaviorReturns Retry-After in seconds and a stable rate_limit_exceeded error code

Error response shape

Every error response uses the same envelope:
{
  "code": "precondition_failed",
  "message": "Resource version mismatch.",
  "retryable": true,
  "details": {
    "expected_resource_version": 1,
    "current_resource_version": 2
  }
}
  • code — stable machine-readable identifier (see catalog below); switch on this, not on message
  • message — short human-readable label, safe to log but not locale-stable
  • retryable — whether retrying the same request with the same idempotency key can succeed
  • details (optional) — endpoint-specific structured context (e.g. expected_resource_version / current_resource_version on precondition_failed). Read variable state from here, not from message.
5xx, 429, and 412 responses are retryable; all other errors are terminal unless the message says otherwise.

Idempotency rules

Use Idempotency-Key on every mutating operation request (POST /operations, PATCH /operations/{id}/complete, PATCH /operations/{id}/void). Current platform rules:
  • retention window is at least 24 hours
  • uniqueness scope is endpoint plus merchant plus authenticated tenant
  • reusing a key with the same payload returns the original response
  • reusing a key with a different payload returns 409 idempotency_key_conflict

Concurrency rules

Operation mutation endpoints (/complete, /void) require If-Match with the latest operation ETag. If you omit it, the API returns 428 precondition_required. If you send a stale value, the API returns 412 precondition_failed with details.expected_resource_version and details.current_resource_version.

Request tracing

Every response includes X-Request-Id. Persist it in your logs and attach it to support cases.

Stable error catalog

Generic

HTTPcodeRetryableWhenOperator action
400invalid_requestNoRequest body / query / path is malformed or fails schema validationFix the request. Check required fields, decimal strings vs integers, ISO 3166-1 alpha-3 country codes (DEU not DE)
401unauthorizedNoMissing, malformed, or revoked Authorization headerCheck the Bearer of_{env}_{country}_… key
403forbiddenNoValid key but not allowed for this tenant, country, or merchantUse a key matching the merchant’s country; verify X-OpenFiskal-Merchant
404not_foundNoResource does not exist or is not visible to the callerConfirm the ID and the merchant scope
412precondition_failedYesStale If-MatchRe-read the operation and retry with the current ETag
422validation_errorNoRequest shape is valid but a field value is semantically wrongFix the value
422regime_validation_failedNoPayload violates regime-specific rules (e.g. KassenSichV/RKSV)Fix onboarding or operation data
428precondition_requiredNoIf-Match is missing on a mutation that requires itRe-read and retry with the current ETag
429rate_limit_exceededYesRequest budget exceededRespect Retry-After and reduce concurrency
500internal_errorYesUnhandled server errorRetry with the same idempotency key; escalate with X-Request-Id if sustained

Conflict (409) variants

All 409 responses use the envelope above, with code distinguishing the cause:
codeWhen
idempotency_key_conflictThe same Idempotency-Key was reused with a different payload
location_has_registersDELETE /locations/{id} blocked — delete or reassign registers first
register_delete_conflictDELETE /registers/{id} blocked — register has operations or is fiscalized
fiscalization_conflictPOST /registers/{id}/fiscalize blocked — register already fiscalized or missing prerequisites (e.g. merchant fiscal_identities)
decommission_conflictPOST /registers/{id}/decommission blocked — register not in a decommissionable state
operation_invalid_state/complete or /void called on an operation that is not open
resource_conflictGeneric fallback for other 409 cases

Retry matrix

ClassRetry?How
5xx, rate_limit_exceeded, precondition_failedYesReuse the same idempotency key. For precondition_failed, re-read to refresh ETag first. For rate_limit_exceeded, honor Retry-After.
All other 4xxNoFix the request and send with a new idempotency key

Next steps