Skip to main content
This guide walks you through a complete integration — from receiving your setup token to completing a fiscalized sale and printing a receipt. The examples use a German merchant (TSE jurisdiction) with sandbox URLs.
All examples use https://sandbox.api.openfiskal.com/v1. Replace with https://api.openfiskal.com/v1 when you go live.

Prerequisites

  • A partnership agreement signed with OpenFiskal
  • The setup token (ofk_setup_...) received via email
  • curl or an HTTP client

Step-by-step

1

Bootstrap your API key

After signing a partnership agreement, OpenFiskal emails a one-time setup token to your technical contact. Exchange it for your platform API key.
curl -X POST https://sandbox.api.openfiskal.com/v1/auth/bootstrap \
  -H "Content-Type: application/json" \
  -d '{
    "setup_token": "ofk_setup_abc123def456",
    "label": "Production"
  }'
{
  "object": "platform_api_key",
  "id": "key_01HXYZ",
  "api_key": "ofk_platform_live_abc123...",
  "label": "Production",
  "created_at": "2026-02-26T10:00:00Z"
}
All subsequent requests use Authorization: Bearer <api_key>.
Store api_key immediately in a secrets manager. It is returned once and cannot be retrieved again. The setup token is now consumed — it cannot be reused.
2

Create an organization

An organization represents the merchant — the legal entity operating the POS.
curl -X POST https://sandbox.api.openfiskal.com/v1/organizations \
  -H "Authorization: Bearer ofk_platform_live_abc123..." \
  -H "Content-Type: application/json" \
  -d '{
    "legal_name": "Mustermann GmbH",
    "country": "DE",
    "tax_id": "DE123456789",
    "address": {
      "line1": "Friedrichstraße 42",
      "city": "Berlin",
      "postal_code": "10117",
      "country": "DE"
    }
  }'
{
  "id": "org_01HXYZ",
  "object": "organization",
  "legal_name": "Mustermann GmbH",
  "country": "DE",
  "tax_id": "DE123456789",
  "address": {
    "line1": "Friedrichstraße 42",
    "city": "Berlin",
    "postal_code": "10117",
    "country": "DE"
  },
  "created_at": "2026-02-26T10:01:00Z"
}
Note the returned id — use it as the OpenFiskal-Organization header on all subsequent requests scoped to this merchant.
3

Create a location

A location is a physical point of sale — a store, restaurant, or kiosk.
curl -X POST https://sandbox.api.openfiskal.com/v1/organizations/org_01HXYZ/locations \
  -H "Authorization: Bearer ofk_platform_live_abc123..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Berlin Mitte",
    "address": {
      "line1": "Friedrichstraße 42",
      "city": "Berlin",
      "postal_code": "10117",
      "country": "DE"
    },
    "timezone": "Europe/Berlin"
  }'
{
  "id": "loc_01HXYZ",
  "object": "location",
  "name": "Berlin Mitte",
  "organization_id": "org_01HXYZ",
  "timezone": "Europe/Berlin",
  "created_at": "2026-02-26T10:02:00Z"
}
4

Create a register

A register is a POS device. Creating one is lightweight — it’s just metadata.
curl -X POST https://sandbox.api.openfiskal.com/v1/locations/loc_01HXYZ/registers \
  -H "Authorization: Bearer ofk_platform_live_abc123..." \
  -H "OpenFiskal-Organization: org_01HXYZ" \
  -H "Content-Type: application/json" \
  -d '{
    "label": "Kasse 1",
    "jurisdiction": "DE"
  }'
{
  "id": "reg_01HXYZ",
  "object": "register",
  "label": "Kasse 1",
  "jurisdiction": "DE",
  "state": "active",
  "active_fiscal_unit": null,
  "location_id": "loc_01HXYZ",
  "created_at": "2026-02-26T10:03:00Z"
}
The register is active but has no fiscal unit yet — it cannot process operations until one is provisioned.
5

Create a fiscal unit

A fiscal unit initializes the fiscal security device. For Germany, this sets up the TSE and establishes the signature chain.
curl -X POST https://sandbox.api.openfiskal.com/v1/registers/reg_01HXYZ/fiscal-units \
  -H "Authorization: Bearer ofk_platform_live_abc123..." \
  -H "OpenFiskal-Organization: org_01HXYZ"
{
  "object": "fiscal_unit_response",
  "fiscal_unit": {
    "id": "fu_01HXYZ",
    "object": "fiscal_unit",
    "register_id": "reg_01HXYZ",
    "state": "active",
    "jurisdiction": "DE",
    "fiscal_registration": {
      "regime": "tse",
      "tse_serial": "abc123def456",
      "client_id": "Kasse 1",
      "signature_algorithm": "ecdsa-plain-SHA384",
      "time_format": "utcTime",
      "registered_at": "2026-02-26T10:03:30Z"
    },
    "provisioned_at": "2026-02-26T10:03:30Z",
    "created_at": "2026-02-26T10:03:30Z"
  },
  "register": {
    "id": "reg_01HXYZ",
    "state": "active",
    "active_fiscal_unit": { "id": "fu_01HXYZ", "state": "active" }
  },
  "register_api_key": null,
  "credential_issued": false
}
The register now has an active fiscal unit and is ready to accept operations.
For server-side integrations, the platform API key already authorizes all register operations. Only pass issue_register_credential: true for device-direct integrations (EV chargers, unattended kiosks).
6

Start a sale

Opening a sale is a fiscal event. The TSE signs the start immediately. Store the returned signature and transaction counter — they must appear on the final receipt.
curl -X POST https://sandbox.api.openfiskal.com/v1/registers/reg_01HXYZ/sales \
  -H "Authorization: Bearer ofk_platform_live_abc123..." \
  -H "OpenFiskal-Organization: org_01HXYZ" \
  -H "Idempotency-Key: sale-table3-order42-start" \
  -H "Content-Type: application/json" \
  -d '{
    "external_id": "table-3-order-42",
    "cashier": {
      "id": "cashier_01",
      "display_name": "Maria R."
    }
  }'
{
  "id": "op_01HXYZ",
  "object": "operation",
  "type": "sale",
  "state": "open",
  "fiscal_state": "succeeded",
  "register_id": "reg_01HXYZ",
  "external_id": "table-3-order-42",
  "started_at": "2026-02-26T12:00:00Z",
  "fiscal": {
    "regime": "tse",
    "fiscal_unit_id": "fu_01HXYZ",
    "tse_serial": "abc123def456",
    "start_event": {
      "signed_at": "2026-02-26T12:00:00Z",
      "transaction_counter": 1,
      "signature": "base64encodedStartSignature=="
    }
  }
}
The fiscal.start_event data is required on the printed receipt. Do not discard it.
7

Add line items

While the sale is open, add items via PATCH. This is not a fiscal event — no signatures are produced. The line_items array replaces the current list entirely.All monetary amounts are integers in the minor currency unit (cents for EUR). Tax rates are decimal strings ("0.19" for 19%).
curl -X PATCH https://sandbox.api.openfiskal.com/v1/sales/op_01HXYZ \
  -H "Authorization: Bearer ofk_platform_live_abc123..." \
  -H "OpenFiskal-Organization: org_01HXYZ" \
  -H "Content-Type: application/json" \
  -d '{
    "line_items": [
      {
        "description": "Wiener Schnitzel",
        "quantity": 2,
        "unit_price_gross": 1800,
        "tax": { "rate": "0.19", "category": "standard" }
      },
      {
        "description": "Apfelschorle",
        "quantity": 2,
        "unit_price_gross": 350,
        "tax": { "rate": "0.19", "category": "standard" }
      }
    ]
  }'
The response includes the resolved line items with computed totals:
{
  "id": "op_01HXYZ",
  "state": "open",
  "data": {
    "line_items": [
      {
        "description": "Wiener Schnitzel",
        "quantity": 2,
        "unit_price_gross": 1800,
        "total_gross": 3600,
        "tax": { "rate": "0.19", "category": "standard", "amount": 575 }
      },
      {
        "description": "Apfelschorle",
        "quantity": 2,
        "unit_price_gross": 350,
        "total_gross": 700,
        "tax": { "rate": "0.19", "category": "standard", "amount": 112 }
      }
    ],
    "totals": {
      "total_gross": 4300,
      "total_net": 3613,
      "total_tax": 687
    }
  }
}
To add an item to an existing list, include all current items plus the new one. The array is a full replacement, not an append.
8

Complete with payment

Completing the sale is a fiscal event. The TSE signs the end, and OpenFiskal generates the fiscal document. At least one payment is required.
curl -X POST https://sandbox.api.openfiskal.com/v1/sales/op_01HXYZ/complete \
  -H "Authorization: Bearer ofk_platform_live_abc123..." \
  -H "OpenFiskal-Organization: org_01HXYZ" \
  -H "Idempotency-Key: sale-table3-order42-complete" \
  -H "Content-Type: application/json" \
  -d '{
    "payments": [
      {
        "method": "card",
        "direction": "inbound",
        "amount": 4300,
        "card": { "scheme": "visa", "last4": "4242" }
      }
    ]
  }'
The response includes the completed operation with the full fiscal document:
{
  "id": "op_01HXYZ",
  "state": "completed",
  "fiscal_state": "succeeded",
  "completed_at": "2026-02-26T12:14:55Z",
  "fiscal": {
    "regime": "tse",
    "fiscal_unit_id": "fu_01HXYZ",
    "document_number": "2026-000001",
    "document_type": "Kassenbeleg",
    "tse_serial": "abc123def456",
    "start_event": {
      "signed_at": "2026-02-26T12:00:00Z",
      "transaction_counter": 1,
      "signature": "base64encodedStartSignature=="
    },
    "end_event": {
      "signed_at": "2026-02-26T12:14:55Z",
      "transaction_counter": 2,
      "signature": "base64encodedEndSignature==",
      "process_type": "Kassenbeleg-V1",
      "process_data": "Beleg^43.00_0.00_0.00_0.00_0.00^43.00:Unbar"
    },
    "verification": {
      "qr_data": "V0;Kasse 1;ecdsa-plain-SHA384;2026-02-26T12:00:00;2026-02-26T12:14:55;43.00;2;base64encodedEndSignature=="
    }
  }
}
9

Retrieve the receipt

The receipt is available as JSON or PDF immediately after completion.
# JSON receipt
curl https://sandbox.api.openfiskal.com/v1/operations/op_01HXYZ/receipt \
  -H "Authorization: Bearer ofk_platform_live_abc123..." \
  -H "OpenFiskal-Organization: org_01HXYZ"
# PDF receipt
curl https://sandbox.api.openfiskal.com/v1/operations/op_01HXYZ/receipt.pdf \
  -H "Authorization: Bearer ofk_platform_live_abc123..." \
  -H "OpenFiskal-Organization: org_01HXYZ" \
  -o receipt.pdf
The JSON receipt is self-contained — it includes issuer details, line items, totals, payments, and the full fiscal document with QR data:
{
  "object": "receipt",
  "issuer": {
    "legal_name": "Mustermann GmbH",
    "tax_id": "DE123456789",
    "location_name": "Berlin Mitte",
    "address": {
      "line1": "Friedrichstraße 42",
      "city": "Berlin",
      "postal_code": "10117",
      "country": "DE"
    }
  },
  "operation": {
    "id": "op_01HXYZ",
    "type": "sale",
    "external_id": "table-3-order-42",
    "cashier": { "id": "cashier_01", "display_name": "Maria R." },
    "started_at": "2026-02-26T12:00:00Z",
    "completed_at": "2026-02-26T12:14:55Z"
  },
  "line_items": ["..."],
  "totals": {
    "total_gross": 4300,
    "total_net": 3613,
    "total_tax": 687
  },
  "payments": ["..."],
  "fiscal": {
    "regime": "tse",
    "fiscal_unit_id": "fu_01HXYZ",
    "verification": {
      "qr_data": "V0;Kasse 1;ecdsa-plain-SHA384;..."
    }
  },
  "links": {
    "self": "https://sandbox.api.openfiskal.com/v1/operations/op_01HXYZ/receipt",
    "pdf": "https://sandbox.api.openfiskal.com/v1/operations/op_01HXYZ/receipt.pdf"
  }
}
Print the fiscal.verification.qr_data value as a QR code on the receipt. In Germany, this is legally required.

What’s next