Quick Start
fiskaly provides fiscal compliance APIs across Europe. Choose your country below to jump straight to the integration guide, or scroll down for a hands-on SIGN DE walkthrough.
Choose your country
Section titled “Choose your country”Germany(SIGN DE)
KassenSichV — TSS, Client, Transaction
Austria(SIGN AT)
RKSV — SCU, Cash Register, Receipt
France(SIGN FR)
NF 525 — Organization, System, Record
Italy(SIGN IT)
RT compliance — Taxpayer, Location, System, Record
Spain(SIGN ES)
TicketBAI & Verifactu — Taxpayer, Signer, Invoice
Portugal(SIGN PT)
AT — Taxpayer, System, Document
Belgium(E-Invoice)
Peppol B2B e-invoicing via the Unified API
Sweden(SIGN SE)
Certificate-based control codes for Swedish fiscal compliance
France, Italy, and Belgium share the Unified API — one integration covers all three. See Integration Planning for effort estimates and rollout timelines.
Hands-on: SIGN DE in 5 minutes
Section titled “Hands-on: SIGN DE in 5 minutes”The walkthrough below uses Germany (SIGN DE) as a concrete example. The flow is: authenticate → create a TSS → sign a transaction.
Germany, Austria, and Spain each have a dedicated Specialized API. France and Italy use the Unified API with a shared resource model. Choose your country above for the right guide.
Time to complete: ~5 minutes with cURL, ~15 minutes if you are building it into application code.
Prerequisites
Section titled “Prerequisites”You need three things before you start:
- A fiskaly account — register free at hub.fiskaly.com
- API credentials — generate an API Key and Secret in the HUB under your organization
- An HTTP client — cURL, Postman, or your language’s HTTP library
Copy the secret immediately when it is generated. If you lose it, you will need to generate a new key pair. Store credentials in environment variables or a secret manager — never in source control.
Watch how to create an API key in the HUB (40 seconds):
Environment
Section titled “Environment”This guide uses the sandbox (TEST) environment. All new organizations start here. No real fiscal data is created, and you will not be billed.
| Sandbox (TEST) | Production (LIVE) | |
|---|---|---|
| Base URL | https://kassensichv-middleware.fiskaly.com/api/v2 | https://kassensichv.fiskaly.com/api/v2 |
| Data | Ephemeral — safe to experiment | Permanent — audit-relevant |
| Billing | Free | Per contract |
| Switching | Default for new orgs | Enable via HUB |
Step 1: Authenticate
Section titled “Step 1: Authenticate”Exchange your API key and secret for a Bearer token.
curl -X POST https://kassensichv-middleware.fiskaly.com/api/v2/auth \
-H "Content-Type: application/json" \
-d '{
"api_key": "YOUR_API_KEY",
"api_secret": "YOUR_API_SECRET"
}'const response = await fetch(
"https://kassensichv-middleware.fiskaly.com/api/v2/auth",
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
api_key: "YOUR_API_KEY",
api_secret: "YOUR_API_SECRET",
}),
}
);
const { access_token, refresh_token } = await response.json();import requests
response = requests.post(
"https://kassensichv-middleware.fiskaly.com/api/v2/auth",
json={
"api_key": "YOUR_API_KEY",
"api_secret": "YOUR_API_SECRET",
},
)
data = response.json()
access_token = data["access_token"]
refresh_token = data["refresh_token"]HttpClient client = HttpClient.newHttpClient();
String body = """
{"api_key":"YOUR_API_KEY","api_secret":"YOUR_API_SECRET"}
""";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://kassensichv-middleware.fiskaly.com/api/v2/auth"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response = client.send(
request, HttpResponse.BodyHandlers.ofString());
// Parse response.body() for access_tokenusing var client = new HttpClient();
var payload = new {
api_key = "YOUR_API_KEY",
api_secret = "YOUR_API_SECRET"
};
var response = await client.PostAsJsonAsync(
"https://kassensichv-middleware.fiskaly.com/api/v2/auth",
payload);
var result = await response.Content
.ReadFromJsonAsync<JsonElement>();
var accessToken = result
.GetProperty("access_token").GetString();Expected response (200 OK):
{ "access_token": "eyJhbGciOiJSUzI1NiIs...", "access_token_expires_in": 86400, "refresh_token": "eyJhbGciOiJSUzI1NiIs...", "refresh_token_expires_in": 172800}The access_token is valid for 24 hours. The refresh_token is valid for 48 hours. Include the access token as Authorization: Bearer <token> in all subsequent requests.
Cache the token and only refresh when you receive a 401 response.
Re-authenticating per request adds unnecessary latency to your checkout flow.
Step 2: Create and initialize a TSS
Section titled “Step 2: Create and initialize a TSS”A TSS (Technical Security System) is the certified signing resource. You need one per physical location. Creating a TSS involves three sub-steps: create, set Admin PIN, and initialize.
a) Create the TSS
TSS_ID=$(uuidgen)
curl -X PUT "https://kassensichv-middleware.fiskaly.com/api/v2/tss/${TSS_ID}" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"description": "My first TSS"
}'const tssId = crypto.randomUUID();
const tssResponse = await fetch(
`https://kassensichv-middleware.fiskaly.com/api/v2/tss/${tssId}`,
{
method: "PUT",
headers: {
"Authorization": `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
description: "My first TSS",
}),
}
);
const tss = await tssResponse.json();
// Save tss.admin_puk — you need it to set the Admin PINExpected response (200 OK) — note the admin_puk field:
{ "_id": "a1b2c3d4-...", "description": "My first TSS", "state": "UNINITIALIZED", "admin_puk": "123456"}b) Set the Admin PIN (using the admin_puk from the response above):
curl -X PATCH "https://kassensichv-middleware.fiskaly.com/api/v2/tss/${TSS_ID}/admin" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"admin_puk": "123456",
"new_admin_pin": "your-secure-admin-pin"
}'await fetch(
`https://kassensichv-middleware.fiskaly.com/api/v2/tss/${tssId}/admin`,
{
method: "PATCH",
headers: {
"Authorization": `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
admin_puk: tss.admin_puk,
new_admin_pin: "your-secure-admin-pin",
}),
}
);c) Authenticate as Admin and initialize:
# Authenticate as admin
curl -X POST "https://kassensichv-middleware.fiskaly.com/api/v2/tss/${TSS_ID}/admin/auth" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{ "admin_pin": "your-secure-admin-pin" }'
# Initialize the TSS
curl -X PATCH "https://kassensichv-middleware.fiskaly.com/api/v2/tss/${TSS_ID}" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{ "state": "INITIALIZED" }'// Authenticate as admin
await fetch(
`https://kassensichv-middleware.fiskaly.com/api/v2/tss/${tssId}/admin/auth`,
{
method: "POST",
headers: {
"Authorization": `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ admin_pin: "your-secure-admin-pin" }),
}
);
// Initialize the TSS
await fetch(
`https://kassensichv-middleware.fiskaly.com/api/v2/tss/${tssId}`,
{
method: "PATCH",
headers: {
"Authorization": `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ state: "INITIALIZED" }),
}
);After initialization, the TSS state changes to INITIALIZED. You are ready to create clients and sign transactions.
Step 3: Create a client
Section titled “Step 3: Create a client”A client represents a single POS terminal or application instance connected to the TSS.
CLIENT_ID=$(uuidgen)
curl -X PUT "https://kassensichv-middleware.fiskaly.com/api/v2/tss/${TSS_ID}/client/${CLIENT_ID}" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{ "serial_number": "POS-001" }'const clientId = crypto.randomUUID();
await fetch(
`https://kassensichv-middleware.fiskaly.com/api/v2/tss/${tssId}/client/${clientId}`,
{
method: "PUT",
headers: {
"Authorization": `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ serial_number: "POS-001" }),
}
);Step 4: Sign a transaction
Section titled “Step 4: Sign a transaction”Transactions have a lifecycle: start (state ACTIVE) then finish (state FINISHED). The finish response contains the cryptographic signature.
a) Start the transaction:
TX_ID=$(uuidgen)
curl -X PUT "https://kassensichv-middleware.fiskaly.com/api/v2/tss/${TSS_ID}/tx/${TX_ID}?tx_revision=1" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"state": "ACTIVE",
"client_id": "YOUR_CLIENT_ID"
}'const txId = crypto.randomUUID();
await fetch(
`https://kassensichv-middleware.fiskaly.com/api/v2/tss/${tssId}/tx/${txId}?tx_revision=1`,
{
method: "PUT",
headers: {
"Authorization": `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
state: "ACTIVE",
client_id: clientId,
}),
}
);b) Finish the transaction (this is where the signature is generated):
curl -X PUT "https://kassensichv-middleware.fiskaly.com/api/v2/tss/${TSS_ID}/tx/${TX_ID}?tx_revision=2" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"state": "FINISHED",
"client_id": "YOUR_CLIENT_ID",
"schema": {
"standard_v1": {
"receipt": {
"receipt_type": "RECEIPT",
"amounts_per_vat_rate": [
{ "vat_rate": "NORMAL", "amount": "10.00" }
],
"amounts_per_payment_type": [
{ "payment_type": "CASH", "amount": "10.00" }
]
}
}
}
}'const finishResponse = await fetch(
`https://kassensichv-middleware.fiskaly.com/api/v2/tss/${tssId}/tx/${txId}?tx_revision=2`,
{
method: "PUT",
headers: {
"Authorization": `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
state: "FINISHED",
client_id: clientId,
schema: {
standard_v1: {
receipt: {
receipt_type: "RECEIPT",
amounts_per_vat_rate: [
{ vat_rate: "NORMAL", amount: "10.00" },
],
amounts_per_payment_type: [
{ payment_type: "CASH", amount: "10.00" },
],
},
},
},
}),
}
);
const signedTx = await finishResponse.json();
console.log(signedTx.signature);Expected response (200 OK) — the key fields are signature and qr_code_data:
{ "_id": "tx-uuid-...", "state": "FINISHED", "number": 1, "time_start": 1700000000, "time_end": 1700000001, "signature": { "value": "dGVzdC1zaWduYXR1cmU=", "algorithm": "ecdsa-plain-SHA384", "counter": 1, "public_key": "BHHz..." }, "qr_code_data": "V0;TSS-ID;TX-NUMBER;..."}The qr_code_data string is what you encode into the QR code printed on the receipt.
Step 5: Log out the Admin
Section titled “Step 5: Log out the Admin”After setup is complete, log the admin out of the TSS:
curl -X POST "https://kassensichv-middleware.fiskaly.com/api/v2/tss/${TSS_ID}/admin/logout" \ -H "Authorization: Bearer ${ACCESS_TOKEN}"Common first-time errors
Section titled “Common first-time errors”| Error | Cause | Fix |
|---|---|---|
401 Unauthorized | Expired or incorrect token | Re-authenticate with /auth. Check that you are using the correct API key for this environment. |
400 E_TSS_NOT_INITIALIZED | Tried to create a client or transaction on an uninitialized TSS | Complete all three TSS setup steps: create, set Admin PIN, initialize. |
400 with “admin_puk” error | Wrong PUK when setting Admin PIN | Use the admin_puk value from the TSS creation response, not a value you chose. |
409 Conflict | Reused a UUID that already exists | Generate a new UUID for each resource (TSS, client, transaction). |
422 E_TX_INVALID_STATE | Tried to finish a transaction that is not ACTIVE | Start the transaction first (revision 1 with state: ACTIVE), then finish (revision 2). |
For the complete error reference, see Error Codes.
Using Postman instead
Section titled “Using Postman instead”If you prefer a GUI-based workflow, download the pre-configured Postman collection:
Download
Get the Postman Collection and Environment files.
Import
Import both files into Postman.Configure
Set your
api_keyandapi_secretin the environment variables.Run
Execute the requests in order — the collection uses variables to chain responses automatically.
See the full Postman Tutorial for a detailed walkthrough.