> ## Documentation Index
> Fetch the complete documentation index at: https://docs.openfiskal.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Getting started · 🇩🇪 Germany

> Onboard a German merchant, fiscalize a register under KassenSichV, and complete a sale with a TSS-signed receipt.

This guide walks the `v1` integration contract for Germany. You create a `deu` API key, onboard a merchant with a German `fiscal_identity`, register a location and terminal, fiscalize the register under KassenSichV, open a register session, start an operation, and complete it with the typed payment contract.

<Info>
  Examples use `https://sandbox.api.openfiskal.com/v1`. Replace with `https://api.openfiskal.com/v1` when you go live.
</Info>

## Prerequisites

* An OpenFiskal tenant
* A tenant-scoped, country-scoped API key (`of_test_deu_…` or `of_live_deu_…`)
* `curl` or an HTTP client
* A backend service / database to store your API key, entity IDs and ETags

## Authentication model

Use the standard bearer header on every request. The key encodes the environment and country — `of_test_deu_…` for the German sandbox, `of_live_deu_…` for German production. A `deu` key rejects payloads for any other country.

```bash theme={null}
-H "Authorization: Bearer of_live_deu_abc123..."
```

Every merchant-scoped request must also include:

```bash theme={null}
-H "X-OpenFiskal-Merchant: merchant_01HXYZ"
```

This header value is the merchant ID returned by `POST /merchants`, not your API key.

## Create resources

Create these resources in order. The register cannot fiscalize until the merchant has a `DEU` `fiscal_identity`.

<Steps>
  <Step title="Create your API key">
    Generate a `deu` key at [console.openfiskal.com](https://console.openfiskal.com). Store it in your secrets manager. Format: `of_{env}_deu_{random}`, e.g. `of_test_deu_abcdefgh12345678`.
  </Step>

  <Step title="Create a merchant">
    `country_code` and every `address.country_code` must be `DEU` (ISO 3166-1 alpha-3). The German `fiscal_identity` requires both `tax_number` (Steuernummer) and `vat_id` (USt-IdNr).

    ```bash theme={null}
    curl -X POST https://sandbox.api.openfiskal.com/v1/merchants \
      -H "Authorization: Bearer of_test_deu_abc123..." \
      -H "Content-Type: application/json" \
      -d '{
        "legal_name": "Mustermann GmbH",
        "country_code": "DEU",
        "address": {
          "line1": "Friedrichstraße 42",
          "city": "Berlin",
          "postal_code": "10117",
          "country_code": "DEU"
        },
        "fiscal_identities": [
          {
            "country_code": "DEU",
            "tax_number": "21/815/08150",
            "vat_id": "DE123456789"
          }
        ]
      }'
    ```

    The returned `id` becomes the `X-OpenFiskal-Merchant` header on every merchant-scoped request that follows.
  </Step>

  <Step title="Create a location">
    A location is a physical point of sale. `timezone` is a **required** IANA zone string.

    ```bash theme={null}
    curl -X POST https://sandbox.api.openfiskal.com/v1/locations \
      -H "Authorization: Bearer of_test_deu_abc123..." \
      -H "X-OpenFiskal-Merchant: merchant_01HXYZ" \
      -H "Content-Type: application/json" \
      -d '{
        "name": "Berlin Mitte",
        "address": {
          "line1": "Friedrichstraße 42",
          "city": "Berlin",
          "postal_code": "10117",
          "country_code": "DEU"
        },
        "timezone": "Europe/Berlin"
      }'
    ```
  </Step>

  <Step title="Create a register">
    A register is the logical checkout or terminal that creates operations.

    ```bash theme={null}
    curl -X POST https://sandbox.api.openfiskal.com/v1/registers \
      -H "Authorization: Bearer of_test_deu_abc123..." \
      -H "X-OpenFiskal-Merchant: merchant_01HXYZ" \
      -H "Content-Type: application/json" \
      -d '{
        "location_id": "loc_01HXYZ",
        "name": "Register 1",
        "external_id": "pos-register-1"
      }'
    ```
  </Step>

  <Step title="Fiscalize the register">
    Before the register can accept operations, call `POST /registers/{id}/fiscalize`. This provisions the KassenSichV components — the TSS and POS client — using the merchant's German `fiscal_identity` and the location's country. The response is `202 Accepted`; poll `GET /registers/{id}` until `fiscalizedAt` is populated.

    ```bash theme={null}
    curl -X POST https://sandbox.api.openfiskal.com/v1/registers/reg_01HXYZ/fiscalize \
      -H "Authorization: Bearer of_test_deu_abc123..." \
      -H "X-OpenFiskal-Merchant: merchant_01HXYZ"
    ```
  </Step>

  <Step title="Verify your saved IDs">
    Persist the returned merchant, location, and register IDs. You will use them for every later operation request.
  </Step>
</Steps>

## Perform a fiscalized sale

Once your register is fiscalized, you can create and complete sale operations.

<Steps>
  <Step title="Open a session">
    Every POS sale, return, or exchange must bind to an open register session. Send a `session_open` operation with the register, currency, and counted opening cash float. Without an open session, `POST /operations` with `source: POS` is rejected.

    ```bash theme={null}
    curl -X POST https://sandbox.api.openfiskal.com/v1/operations \
      -H "Authorization: Bearer of_test_deu_abc123..." \
      -H "X-OpenFiskal-Merchant: merchant_01HXYZ" \
      -H "Idempotency-Key: ses-2026-04-30-reg_01HXYZ-open" \
      -H "Content-Type: application/json" \
      -d '{
        "type": "session_open",
        "register_id": "reg_01HXYZ",
        "currency": "EUR",
        "opening_balance_amount": "50.00"
      }'
    ```

    Persist the returned `session_id`. See [Sessions](/sessions) for cash adjustments, end-of-shift counting, and closing.
  </Step>

  <Step title="Start an operation">
    `POST /operations` is a single-shot create. All monetary fields are decimal strings, not integers. `pretax_amount + tax_amount + tip_amount` must equal `total_amount`. `line_items` is required with at least one entry.

    ```bash theme={null}
    curl -X POST https://sandbox.api.openfiskal.com/v1/operations \
      -H "Authorization: Bearer of_test_deu_abc123..." \
      -H "X-OpenFiskal-Merchant: merchant_01HXYZ" \
      -H "Idempotency-Key: op-order-1001-start" \
      -H "Content-Type: application/json" \
      -d '{
        "register_id": "reg_01HXYZ",
        "source": "POS",
        "type": "sale",
        "currency": "EUR",
        "external_id": "order-1001",
        "pretax_amount": "39.72",
        "tax_amount": "2.78",
        "tip_amount": "0.00",
        "total_amount": "42.50",
        "line_items": [
          {
            "title": "Lunch menu",
            "sku_identifier": "MENU-LUNCH",
            "quantity": 1,
            "unit_price": "42.50",
            "total_amount": "42.50",
            "taxes": [
              { "name": "MwSt. 7%", "rate": "0.07", "tax_amount": "2.78" }
            ]
          }
        ]
      }'
    ```

    <Warning>
      Sending `line_items[].taxes[]` is required.
    </Warning>

    Response:

    ```http theme={null}
    HTTP/1.1 201 Created
    ETag: "1"
    X-Request-Id: req_01JSTART
    ```

    ```json theme={null}
    {
      "id": "op_01HXYZ",
      "resource_version": 1,
      "merchant_id": "merchant_01HXYZ",
      "location_id": "loc_01HXYZ",
      "register_id": "reg_01HXYZ",
      "source": "POS",
      "external_id": "order-1001",
      "status": "open",
      "type": "sale",
      "currency": "EUR",
      "pretax_amount": "39.72",
      "tax_amount": "2.78",
      "tip_amount": "0.00",
      "total_amount": "42.50",
      "line_items": [
        {
          "id": "li_01HXYZ",
          "title": "Lunch menu",
          "sku_identifier": "MENU-LUNCH",
          "quantity": 1,
          "unit_price": "42.50",
          "total_amount": "42.50",
          "taxes": [
            { "id": "lit_01HXYZ", "name": "MwSt. 7%", "rate": "0.07", "tax_amount": "2.78" }
          ]
        }
      ],
      "payments": [],
      "created_at": "2026-02-26T12:00:00Z",
      "updated_at": "2026-02-26T12:00:00Z"
    }
    ```
  </Step>

  <Step title="Complete the operation">
    Completion includes typed payment legs. Use one entry per tender leg and include processor references where available. Send the latest `ETag` in `If-Match`. The completed operation returns `fiscal_information` with the KassenSichV regime.

    <Note>
      Operation bodies are immutable after creation — there is no endpoint to amend them. Build the full line-item and amount set before calling `POST /operations`. Open operations only accept `POST .../complete` and `POST .../void`.
    </Note>

    ```bash theme={null}
    curl -X POST https://sandbox.api.openfiskal.com/v1/operations/op_01HXYZ/complete \
      -H "Authorization: Bearer of_test_deu_abc123..." \
      -H "X-OpenFiskal-Merchant: merchant_01HXYZ" \
      -H "Idempotency-Key: op-order-1001-complete" \
      -H 'If-Match: "1"' \
      -H "Content-Type: application/json" \
      -d '{
        "payments": [
          {
            "payment_id": "pay_1001_card",
            "method": "card",
            "amount": "42.50",
            "currency": "EUR",
            "status": "captured",
            "processor": "sumup",
            "card_brand": "visa",
            "processor_reference": "ch_123",
            "processed_at": "2026-02-26T12:05:00Z"
          }
        ]
      }'
    ```

    Response:

    ```http theme={null}
    HTTP/1.1 200 OK
    ETag: "2"
    X-Request-Id: req_01JCOMPLETE
    ```

    ```json theme={null}
    {
      "id": "op_01HXYZ",
      "resource_version": 2,
      "status": "completed",
      "payments": [
        {
          "payment_id": "pay_1001_card",
          "method": "card",
          "amount": "42.50",
          "currency": "EUR",
          "status": "captured",
          "processor": "sumup",
          "processor_reference": "ch_123",
          "card_brand": "visa",
          "processed_at": "2026-02-26T12:05:00Z"
        }
      ],
      "fiscal_information": {
        "regime": "KassenSichV",
        "document_number": "2026-000123",
        "document_type": "Kassenbeleg",
        "tss_serial_number": "TSE0123456789ABCDEF",
        "pos_client_serial_number": "Kasse1",
        "signature_algorithm": "ecdsa-plain-SHA384",
        "time_format": "utcTime",
        "start_event": {
          "signed_at": "2026-02-25T19:02:11Z"
        },
        "end_event": {
          "signed_at": "2026-02-25T20:14:55Z",
          "transaction_counter": 4822,
          "signature": "base64encodedEndSignature==",
          "public_key": "base64encodedPublicKey==",
          "process_type": "Kassenbeleg-V1",
          "process_data": "Beleg^42.50_0.00_0.00_0.00_0.00^42.50:Bar"
        },
        "verification": {
          "qr_data": "V0;Kasse1;ecdsa-plain-SHA384;2026-02-25T19:02:11;2026-02-25T20:14:55;42.50;4822;base64encodedEndSignature=="
        }
      }
    }
    ```
  </Step>
</Steps>

## Void an open operation

If the sale is abandoned before completion, void the open operation:

```bash theme={null}
curl -X POST https://sandbox.api.openfiskal.com/v1/operations/op_01HXYZ/void \
  -H "Authorization: Bearer of_test_deu_abc123..." \
  -H "X-OpenFiskal-Merchant: merchant_01HXYZ" \
  -H "Idempotency-Key: op-order-1001-void" \
  -H 'If-Match: "1"' \
  -H "Content-Type: application/json" \
  -d '{ "reason": "void_before_completion" }'
```

## Next steps

* [Transaction lifecycle](/pos-operation-ingestion) — operation lifecycle in detail
* [Payment lifecycle](/payment-lifecycle) — split tender, asynchronous settlement
