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.
Register sessions: new operation types + session-bound POS operationsThree new operation types model the cash-drawer shift on a fiscalized register:
session_open— start a shift with a counted opening floatsession_cash_adjustment— record mid-shift cash movement (drop, pay-in, correction)session_close— end the shift with a merchant-counted closing balance
POST /operations (no separate sessions endpoint) and carry the same resource_version / ETag mechanics as goods-movement operations. They are born status: "completed" — there is no PATCH .../complete step for session events.Breaking — POS goods-movement now requires an open session. Sending POST /operations with source: "POS" on a register that has no open session is rejected with 409 no_open_session. Open the register’s session with session_open before posting POS sales, returns, or exchanges. At most one open session per register; opening a second while one is open returns 409 session_invalid_state.New field — session_id on operation responses. Every operation response now carries session_id on the envelope. For POS goods-movement, it’s the session the operation was rung up during. For session-event variants, it’s the session being opened/adjusted/closed. For ONLINE goods-movement (no register), it’s null.Breaking — GET /operations/{id} is now a discriminated union on type. Six variants: SaleOperation, ReturnOperation, ExchangeOperation, SessionOpenOperation, SessionCashAdjustmentOperation, SessionCloseOperation. Each variant carries only the fields meaningful to its type — SESSION_* responses no longer include zero-padded pretax_amount / total_amount / line_items / payments / fiscal_information, and goods-movement responses no longer include null session_*_details placeholders. SALE responses also drop the always-null related_operation_id and external_related_operation.See Sessions for the full walkthrough.Update integrators that issue POS operations to bracket each shift with session_open / session_close before your next release.Merchant
tax_id removed; Italian fiscal_identity reshapedThe top-level Merchant.tax_id field has been removed from create, update, and response shapes. Tax identifiers now live exclusively on fiscal_identity, where they can vary by country.The Italian fiscal_identity object (country_code: "ITA") has been replaced:- Removed (both optional):
vat_id,codice_fiscale. - Added (all required):
tax_number(Codice Fiscale — 11 numeric digits or 16 alphanumeric characters),vat_number(Partita IVA — 11 numeric digits),fisconline_user(16 alphanumeric characters; the Codice Fiscale of the person delegated to access the Agenzia delle Entrate portal),fisconline_password,fisconline_pin. legal_entity_type(COMPANY | INDIVIDUAL) is unchanged.
DEU) and Austrian (AUT) fiscal_identity shapes are unchanged.Update integrator code that creates or updates Italian merchants, and stop sending the top-level tax_id field for all merchants, before your next release.New endpoints:
POST /exports, GET /exports/{exportId}Two new endpoints expose asynchronous fiscal export jobs.POST /exports enqueues a job and returns 202 Accepted with { id, type, status: "pending", register_id?, from, to, created_at }. Required body fields: type (dsfinvk is the first available type), from and to (ISO 8601 date-times). Optional register_id scopes the export to a single register; when omitted, the export covers all eligible registers under the merchant for the matching fiscal regime.GET /exports/{exportId} returns the job. Once status is completed, download_url is a signed URL to fetch the artifact. When status is failed, error.message (and optional error.details) describes why.Both endpoints require the X-OpenFiskal-Merchant header.external_related_operation accepted on returns/exchangesPOST /operations with type: "return" or type: "exchange" now accepts a new optional field, external_related_operation, for cases where the original sale was never ingested into OpenFiskal (typically during platform migration before backfill).- Mutually exclusive with
related_operation_id— exactly one must be set on a return or exchange. - Both fields on the nested object are required strings.
- The same field is echoed on the operation response.
related_operation_id when the original sale exists in OpenFiskal; use external_related_operation otherwise.Sign convention enforced on
total_amount and line_items[].total_amountPOST /operations now rejects payloads whose amount signs do not match Operation.type:sale:total_amountand everyline_items[].total_amountmust be>= 0.return:total_amountand everyline_items[].total_amountmust be<= 0.exchange: no sign constraint (an exchange may net positive, negative, or zero).
sale (freebie) and return (zero-value return). Violations return 422 Unprocessable Entity.Sign is the only on-wire signal Fiskaly’s TSE has to distinguish a sale from a return signature, so OpenFiskal enforces it before signing.Update return flows that previously sent positive amounts on type: "return" to send non-positive amounts before your next release.fiscal_identities[].country renamed to country_code; DE fields now requiredThe discriminator field on every fiscal_identity entry has been renamed from country to country_code. This applies to all three country variants (DEU, AUT, ITA) on both the create/update payload and the merchant response. Payloads that send country will reject.For DEU fiscal identities, tax_number (Steuernummer) and vat_id (USt-IdNr) are now both required. Previously both were optional. AUT and ITA requirements are unchanged.Update integrator code that creates or parses merchant fiscal_identities before your next release.OpenAPI JSON now served at
/openapi-jsonThe current production OpenAPI spec is now served at:- Production:
https://api.openfiskal.com/openapi-json - Sandbox:
https://sandbox.api.openfiskal.com/openapi-json
/docs-json is preserved as a 301 redirect to the new path. Import the new URL into Postman, Insomnia, or an OpenAPI code generator to scaffold a client.The OpenAPI spec always matches the live API implementation.Operation.status enum corrected; 412 payload includes expected_resource_versionThe Operation.status enum has been corrected from 'open' | 'completed' | 'cancelled' to 'open' | 'completed' | 'voided'. Operations that you PATCH /operations/{id}/void resolve to status: "voided", not cancelled. There were never any operations with status: "cancelled" in production — the OpenAPI value was wrong.412 Precondition Failed error bodies now include details.expected_resource_version alongside details.current_resource_version. The message field is the literal string "Resource version mismatch." (previously "The supplied If-Match value is stale.").Update Operation.status parsers to accept voided, and update any UI that surfaces the precondition_failed error message string.Operation type
refund renamed to returnThe Operation.type enum now uses return instead of refund for operations where goods come back from the customer. The valid values are sale | return | exchange.OpenFiskal now distinguishes goods movement from money movement: operations model goods (sale/return/exchange), refund is reserved for money movement (a negative payment transaction on an operation) and is not an operation type. This unlocks flows the old terminology could not express cleanly — return with store credit (goods back, no money), goodwill refund (money back, no goods), or partial refunds on kept items.POST /operationswithtype: "refund"will now reject. Usetype: "return".- Response shapes now return
type: "return"for what was previouslytype: "refund". Payment.statusenum is unchanged —refundedis still the correct status for a payment that has been reversed, because it describes money movement.
KassenSichV schema changes
FiscalInformationKassenSichVVerification: renamedtse_serialtotss_serial_numberandclient_idtopos_client_serial_number. Both remain required.FiscalInformationKassenSichVEndEvent: added requiredpublic_key(string).FiscalInformationKassenSichVStartEvent: removedtransaction_counterandsignature.signed_atis now the only required field.
Country code casingClarified that the
country field on Address and on the legal entity schema must be an ISO 3166-1 alpha-3 code in uppercase (for example, DEU). No behavior change; existing uppercase values continue to validate.New endpoint:
POST /registers/{registerId}/decommissionPermanently retires a fiscalized register. The endpoint calls the underlying TSE provider to decommission the register’s fiscal components and returns the updated register with decommissionedAt populated. Currently DE/KassenSichV only; AT/IT will follow as those regimes ship publicly.Conflicts return 409 decommission_conflict:- Register has not been fiscalized.
- Register has already been decommissioned.