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.
Overview
A register session is the cash-drawer shift on a fiscalized register. You open a session at the start of the shift, every POS goods-movement that follows binds to it, and you close the session at end of shift with a counted cash balance. Sessions exist only on POS — ONLINE operations have no register and therefore no session.
Sessions are not created with their own endpoint. They are opened, adjusted, and closed by sending session-event operations through the same POST /operations endpoint that ingests sales, returns, and exchanges. Under many regimes a session open, cash adjustment, or close is itself a fiscal event that must be tracked, persisted, and signed alongside goods movements — routing them through /operations is what gives them the same lifecycle, idempotency, and signature surface as a sale.
Session states
open — the session accepts goods-movement operations and cash adjustments
closed — the session is final; new operations on the register need a new session_open
A register has at most one open session at a time. Opening a second session while one is still open is rejected.
Session-event operation types
| Type | Purpose |
|---|
session_open | Start a new shift on a register with a counted opening float |
session_cash_adjustment | Record a mid-shift cash movement (drop, pay-in, till correction) |
session_close | End the shift with a merchant-counted closing balance |
All three flow through POST /operations, return an operation resource, and carry the same resource_version / ETag mechanics as goods-movement operations.
Opening a session
Send a session_open operation with the register, currency, and counted opening float.
curl -X POST https://api.openfiskal.com/v1/operations \
-H "Authorization: Bearer $API_KEY" \
-H "X-OpenFiskal-Merchant: merchant_01HXYZ" \
-H "Idempotency-Key: ses-2026-04-30-reg_abc123-open" \
-H "Content-Type: application/json" \
-d '{
"type": "session_open",
"register_id": "reg_abc123",
"currency": "EUR",
"opening_balance_amount": "50.00",
"opening_note": "Float supplied by office manager."
}'
The response carries the new session_id. Store it — every operation you send on this register until close binds to it automatically.
{
"id": "op_ses_open_001",
"type": "session_open",
"register_id": "reg_abc123",
"session_id": "ses_abc123",
"status": "completed",
"currency": "EUR",
"resource_version": 1
}
The operation status is completed because session-event operations are final the moment OpenFiskal accepts them — there is no later PATCH .../complete step. Don’t confuse it with the session’s lifecycle state (the open / closed flag tracked separately).
Goods movements bind to the session
Every sale, return, and exchange you send on a POS register carries the session_id of the open session in the response. You do not set session_id on the request — OpenFiskal resolves it from the register’s currently open session.
If you call POST /operations with source: POS on a register that has no open session, the request is rejected.
Mid-shift cash adjustments
Use session_cash_adjustment for any cash movement that is not a sale or return: cash drops to the safe, pay-ins, till corrections. The cash_amount is signed — negative for cash out, positive for cash in. Multiple adjustments per session are allowed.
curl -X POST https://api.openfiskal.com/v1/operations \
-H "Authorization: Bearer $API_KEY" \
-H "X-OpenFiskal-Merchant: merchant_01HXYZ" \
-H "Idempotency-Key: ses_abc123-adj-001" \
-H "Content-Type: application/json" \
-d '{
"type": "session_cash_adjustment",
"register_id": "reg_abc123",
"currency": "EUR",
"cash_amount": "-20.00",
"note": "Cash drop to safe."
}'
Closing a session
Send session_close with the merchant-counted closing balance. OpenFiskal compares it against the expected balance (opening float + cash sales − cash returns + adjustments) and records any discrepancy.
curl -X POST https://api.openfiskal.com/v1/operations \
-H "Authorization: Bearer $API_KEY" \
-H "X-OpenFiskal-Merchant: merchant_01HXYZ" \
-H "Idempotency-Key: ses_abc123-close" \
-H "Content-Type: application/json" \
-d '{
"type": "session_close",
"register_id": "reg_abc123",
"currency": "EUR",
"counted_closing_amount": "127.50",
"discrepancy_note": "Two coins from the day before were unaccounted for."
}'
Once the close is completed, the register has no open session. The next shift starts with a new session_open and a fresh session_id.
Walkthrough: a single shift
The scenario: a German bakery opens at 08:00 with a 50 EUR float, rings up two cash sales, drops 20 EUR to the safe at midday, and closes the till at 17:00 with 127.50 EUR counted.
Open the session with a 50 EUR float
curl -X POST https://api.openfiskal.com/v1/operations \
-H "Authorization: Bearer $API_KEY" \
-H "X-OpenFiskal-Merchant: merchant_01HXYZ" \
-H "Idempotency-Key: ses-2026-04-30-reg_abc123-open" \
-H "Content-Type: application/json" \
-d '{
"type": "session_open",
"register_id": "reg_abc123",
"currency": "EUR",
"opening_balance_amount": "50.00"
}'
Response (abbreviated):{
"id": "op_01_open",
"type": "session_open",
"session_id": "ses_abc123",
"register_id": "reg_abc123",
"status": "completed"
}
Capture ses_abc123 — every operation that follows on this register binds to it automatically.Ring up the first sale (4.50 EUR)
Open the sale:curl -X POST https://api.openfiskal.com/v1/operations \
-H "Authorization: Bearer $API_KEY" \
-H "X-OpenFiskal-Merchant: merchant_01HXYZ" \
-H "Idempotency-Key: order-1001-start" \
-H "Content-Type: application/json" \
-d '{
"type": "sale",
"source": "POS",
"register_id": "reg_abc123",
"currency": "EUR",
"external_id": "order-1001",
"pretax_amount": "4.21",
"tax_amount": "0.29",
"tip_amount": "0.00",
"total_amount": "4.50",
"line_items": [
{
"title": "Brötchen",
"quantity": 3,
"unit_price": "1.50",
"total_amount": "4.50",
"taxes": [
{ "name": "MwSt 7%", "rate": "0.07", "tax_amount": "0.29" }
]
}
]
}'
The response carries "session_id": "ses_abc123" even though you did not send it, and the operation comes back with "status": "open". Capture the returned id and resource_version for the complete step.Complete the sale with a cash payment so it counts toward the session’s expected closing balance:curl -X PATCH https://api.openfiskal.com/v1/operations/op_01_sale_4.50/complete \
-H "Authorization: Bearer $API_KEY" \
-H "X-OpenFiskal-Merchant: merchant_01HXYZ" \
-H "If-Match: \"1\"" \
-H "Idempotency-Key: order-1001-complete" \
-H "Content-Type: application/json" \
-d '{
"payments": [
{
"payment_id": "pay_1001",
"method": "cash",
"amount": "4.50",
"currency": "EUR",
"status": "captured"
}
]
}'
The response is now "status": "completed" and (on a fiscalized German register) carries a TSE signature in fiscal_information. Ring up the second sale (98.00 EUR)
curl -X POST https://api.openfiskal.com/v1/operations \
-H "Authorization: Bearer $API_KEY" \
-H "X-OpenFiskal-Merchant: merchant_01HXYZ" \
-H "Idempotency-Key: order-1002-start" \
-H "Content-Type: application/json" \
-d '{
"type": "sale",
"source": "POS",
"register_id": "reg_abc123",
"currency": "EUR",
"external_id": "order-1002",
"pretax_amount": "91.59",
"tax_amount": "6.41",
"tip_amount": "0.00",
"total_amount": "98.00",
"line_items": [
{
"title": "Catering tray",
"quantity": 1,
"unit_price": "98.00",
"total_amount": "98.00",
"taxes": [
{ "name": "MwSt 7%", "rate": "0.07", "tax_amount": "6.41" }
]
}
]
}'
Complete the sale the same way the first one was completed (PATCH /v1/operations/{id}/complete with If-Match: "1" and a payments array). Both sales must be completed for their cash to count toward the expected closing balance below.Drop 20 EUR to the safe at midday
curl -X POST https://api.openfiskal.com/v1/operations \
-H "Authorization: Bearer $API_KEY" \
-H "X-OpenFiskal-Merchant: merchant_01HXYZ" \
-H "Idempotency-Key: ses_abc123-drop-001" \
-H "Content-Type: application/json" \
-d '{
"type": "session_cash_adjustment",
"register_id": "reg_abc123",
"currency": "EUR",
"cash_amount": "-20.00",
"note": "Cash drop to safe."
}'
Expected balance now: 50.00 + 4.50 + 98.00 − 20.00 = 132.50 EUR.Close the session with a counted 127.50 EUR
curl -X POST https://api.openfiskal.com/v1/operations \
-H "Authorization: Bearer $API_KEY" \
-H "X-OpenFiskal-Merchant: merchant_01HXYZ" \
-H "Idempotency-Key: ses_abc123-close" \
-H "Content-Type: application/json" \
-d '{
"type": "session_close",
"register_id": "reg_abc123",
"currency": "EUR",
"counted_closing_amount": "127.50",
"discrepancy_note": "Five euro short — investigating."
}'
OpenFiskal records expected 132.50 EUR, counted 127.50 EUR, and a −5.00 EUR discrepancy. The session is now closed. The next shift on reg_abc123 starts with a new session_open and a fresh session_id.
Next steps