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)
InfraSec TCS — certificate-based control codes
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.
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.
What to build next
Section titled “What to build next”Was this page helpful?