> ## 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.

# Authentication

> How authentication, merchant scoping, idempotency, and conditional writes work in the OpenFiskal API.

## API scope

<Warning>
  The OpenFiskal API currently supports server-to-server communication only.
  Do not embed API keys in POS devices, mobile apps, browsers, or other client-side surfaces.
</Warning>

## API key creation

Create API keys at [console.openfiskal.com](https://console.openfiskal.com). Each key is tenant-scoped and country-scoped.

<Warning>
  Store the key in your secrets manager. Treat it like any other production secret.
</Warning>

## Key format

Keys follow the pattern `of_{environment}_{country}_{random}` where:

* `environment` is `test` (sandbox) or `live` (production)
* `country` is the lowercase ISO 3166-1 alpha-3 country code (e.g. `deu`, `aut`)
* `random` is the opaque secret portion

Examples:

* `of_test_deu_abcdefgh12345678` — sandbox key for Germany
* `of_live_deu_…` — production key for Germany
* `of_test_aut_…` — sandbox key for Austria

The embedded country scopes every request. A `deu` key cannot create merchants in Austria.

## Use the standard authorization header

Although the credential is an API key, the API uses the standard HTTP bearer pattern:

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

This keeps the API compatible with standard API tooling, gateways, proxies, and security middleware.

## Merchant scoping

For every merchant-scoped request, also send:

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

This header must contain the OpenFiskal merchant ID, not the API key.

Together, a typical merchant-scoped request looks like this:

```bash theme={null}
curl https://api.openfiskal.com/v1/operations \
  -H "Authorization: Bearer of_live_deu_abc123..." \
  -H "X-OpenFiskal-Merchant: merchant_01HXYZ"
```

Merchant-scoped resources include:

* Locations
* Registers
* Operations

You do not need `X-OpenFiskal-Merchant` when you create, update, delete, or read the merchant itself.

## Response envelope

Single-resource endpoints return the resource object directly. List endpoints wrap the array in a `data` field:

```json theme={null}
{
  "data": [
    { "id": "merchant_01HXYZ", "legal_name": "Mustermann GmbH", "..." : "..." },
    { "id": "merchant_01HABC", "legal_name": "Beispiel AG", "..." : "..." }
  ]
}
```

This applies to `GET /merchants`, `GET /registers`, and `GET /locations`. Single-resource responses (`GET /merchants/{id}`, `POST /operations`, etc.) return the object at the top level without a `data` wrapper.

Error responses always use the [error envelope](/errors#response-shape), regardless of the endpoint.

## Idempotency

For mutating requests that create or finalize state, send an `Idempotency-Key` header so retries remain safe:

```bash theme={null}
curl -X POST https://api.openfiskal.com/v1/operations \
  -H "Authorization: Bearer of_live_deu_abc123..." \
  -H "X-OpenFiskal-Merchant: merchant_01HXYZ" \
  -H "Idempotency-Key: op-order-1001-start" \
  -H "Content-Type: application/json" \
  -d '{ "...": "..." }'
```

Use idempotency keys on every `POST /operations`, `POST /operations/{id}/complete`, and `POST /operations/{id}/void` request.

The platform retains keys for at least 24 hours. Reusing the same key with a different payload returns `409 idempotency_key_conflict`.

## Recommended handling

* Return `401` when authentication is missing or invalid.
* Return `403` when the API key is valid but not allowed to access the requested tenant, country, or merchant.
* Return `412 Precondition Failed` when an operation update, completion, or cancellation uses a stale `If-Match` value.
* Return `429 Too Many Requests` with `Retry-After` when the caller exceeds `500 requests/second` per API key.

## Next step

Continue with [Getting started](/getting-started) for the full onboarding and operation flow.
