Skip to content

API documentation

Connect your own system to Zaksio: read and write products, counterparties and invoices, check stock levels and receive events by webhook. Document version: 1.0.0.

Quick start

The public Zaksio API has the base address https://api.zaksio.pl and the version in the path (/v1). Current version of the document: 1.0.0. We will introduce breaking changes only as a new path version; new fields in responses and new endpoints may appear within /v1, so ignore fields you do not recognise.

  1. In the app, go to Settings, "API and integrations", click "New key", choose the company and scopes, confirm with your password and copy the key (we show it only once).
  2. Save the key in an environment variable, e.g. ZAKSIO_API_KEY, and check that it works:
curl https://api.zaksio.pl/v1/whoami \
  -H "Authorization: Bearer $ZAKSIO_API_KEY"

The response shows the company the key belongs to and its scopes. From here on, use the addresses described in the "API addresses" section.

Authentication

Every request requires the header Authorization: Bearer zk_live_.... The key acts on behalf of the person who created it, but only within the selected scopes, and it never has more permissions than that person (checked on every request). The key is tied to one company: all endpoints operate on that company's data. App session tokens do not work in /v1, and API keys do not work outside /v1.

  • Missing or invalid key: 401 (codes missing_api_key, invalid_api_key).
  • Revoked or expired key: 401 (api_key_revoked, api_key_expired).
  • IP address outside the key's list: 403 (ip_not_allowed).
  • Missing scope: 403 (insufficient_scope).
  • Licence in read-only mode: writes return 402 (licence_read_only).

Scopes

The scope defines what the key can do. A key can never do more than the permissions of the person who created it.

API key scopes
ScopeDescription
products:readList and details of products from the catalogue.
products:writeDodawanie i zmiana produktow w kartotece.
stock:readStany magazynowe produktow (suma i podzial na magazyny).
contractors:readList and details of counterparties.
contractors:writeDodawanie, zmiana i usuwanie kontrahentow.
invoices:readList, details and PDF of sales invoices.
invoices:writeCreating and issuing sales invoices (numbering and posting as in the app).

Key limits and security

  • Request limit: 120 per minute per key by default (configurable when creating the key, up to 6000). Every response has the headers RateLimit-Limit, RateLimit-Remaining i RateLimit-Reset (seconds until the end of the window). When exceeded: 429 (rate_limited) with the header Retry-After. Repeat the request after this time.
  • Failed authentication: after 30 failed attempts per minute from one IP address, further ones receive 429 (too_many_failed_auth).
  • IP address list, expiry, rotation and revocation: you set them in the app. Rotation creates a new key, and the old one keeps working for the transition period you choose; revocation takes effect immediately.
  • Retention: do not put the key in browser or mobile app code; use it only on the server side.

Conventions: amounts, dates, pagination

  • Amounts are integers in grosze (12300 = 123.00 PLN).
  • Quantities is a decimal string with a dot, e.g. "2", "14.5", with a maximum of four decimal places.
  • Dates is RRRR-MM-DD, and timestamps are ISO 8601 in UTC. Identifiers are UUIDs.
  • Cursor pagination: lists accept limit (1 to 200, default 50), cursor i order (asc oldest first, desc newest first). The response has data i pagination with the fields hasMore and nextCursor; you fetch the next page by passing nextCursor as cursor. The cursor is opaque: do not parse it. Records added while you are paging do not shift the pages.
  • Partial update (PATCH): you send only the fields to change. Omitted fields stay unchanged.

Write idempotency

Write requests (POST, PATCH, DELETE) accept the header Idempotency-Key (up to 255 ASCII characters, e.g. a UUID). The first request with a given key is executed normally, and its response is stored for 24 hours. Repeating the same request with the same key returns the stored response with the header Idempotent-Replayed: true and does not perform the write a second time. This lets you safely retry a request after a network error, e.g. you will not issue two invoices.

  • Same key with different data: 422 (idempotency_key_reuse).
  • Same key while the first request is still in progress: 409 (idempotency_in_progress), retry after a moment.
  • 5xx and 429 errors are not stored, so a retry will be executed from scratch.
  • Idempotency keys are independent for each API key.

Errors

Errors use the RFC 9457 format (application/problem+json). Base your handling on the field code and the HTTP code, not on the text detail. Field requestId (also in the header X-Request-Id) include in your support request.

{
  "type": "https://zaksio.pl/dokumentacja-api#blad-insufficient-scope",
  "title": "Brak dostepu",
  "status": 403,
  "detail": "Klucz nie ma zakresu products:write (albo tworca klucza utracil odpowiednie uprawnienie)",
  "instance": "/v1/products",
  "code": "insufficient_scope",
  "requestId": "req-1a2b3c"
}

For validation_error (400) field errors contains a list of invalid fields with the pointer pointer, e.g. /lines/0/quantity.

Error codes
Code (code)HTTPMeaning
missing_api_key401Missing Authorization: Bearer zk_live_... header
invalid_api_key401The key has an invalid format or does not exist
api_key_revoked401The key has been revoked
api_key_expired401The key has expired (also after the rotation grace period)
api_key_owner_removed401The person who created the key no longer belongs to the organisation
ip_not_allowed403The request IP address is not on the key's allowlist
insufficient_scope403The key lacks the scope required by the operation (or the key's creator has lost the permission)
feature_disabled403The public API is disabled for the organisation
module_disabled403The module (catalogue, warehouse) is not enabled in the business profile
tenant_deleted403The organisation has been deleted
licence_read_only402Licence expired or payment overdue: read-only access allowed
document_limit_reached402Wyczerpano miesieczny limit dokumentow w planie
validation_error400Invalid data; list of fields in errors[]
invalid_cursor400The pagination cursor is invalid
invalid_idempotency_key400Idempotency-Key contains disallowed characters or is too long
not_found404No such endpoint in API v1
product_not_found404Product not found
contractor_not_found404Counterparty not found (when creating an invoice: 422)
invoice_not_found404Invoice not found
sku_or_ean_taken409The SKU or EAN code is already used in this company
product_has_stock_history409Batch tracking cannot be changed for a product with movement history
contractor_in_use409The counterparty appears on documents and cannot be deleted
invoice_not_draft409An issued invoice cannot be changed or issued again
period_closed409The accounting period is closed
idempotency_in_progress409A request with this Idempotency-Key is currently being processed; retry shortly
idempotency_key_reuse422Ten Idempotency-Key byl juz uzyty z innymi danymi zadania
invalid_service422A service has no batches, expiry date or ADR attributes
exemption_basis_required422VAT-exempt items (zw) require an exemption basis
non_positive_total422The invoice amount must be positive
too_many_failed_auth429Zbyt wiele blednych prob uwierzytelnienia z jednego adresu
rate_limited429Per-minute request limit exceeded for the key
maintenance503Tryb serwisowy: zapisy chwilowo zablokowane
internal_error500Server error; include the requestId in your report

API addresses

Account

Informacje o kluczu API uzytym w zadaniu.

Informacje o kluczu

GET/v1/whoami

Required scope: none (any valid key is enough)

Returns the company, scopes and limit of the current key. Useful for checking whether the key works and what it gives access to. Requires no scope.

Request example

curl
curl -X GET "https://api.zaksio.pl/v1/whoami" \
  -H "Authorization: Bearer $ZAKSIO_API_KEY"
JavaScript (Node.js)
const response = await fetch("https://api.zaksio.pl/v1/whoami", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.ZAKSIO_API_KEY}`,
  },
});

const data = await response.json();
if (!response.ok) throw new Error(`${data.code}: ${data.detail}`); // application/problem+json
console.log(data);
Python
import os
import requests

response = requests.request(
    "GET",
    "https://api.zaksio.pl/v1/whoami",
    headers={
        "Authorization": f"Bearer {os.environ['ZAKSIO_API_KEY']}",
    },
    timeout=30,
)

if not response.ok:
    problem = response.json()  # application/problem+json
    raise RuntimeError(f"{problem['code']}: {problem['detail']}")
print(response.json())
PHP
<?php
$ch = curl_init('https://api.zaksio.pl/v1/whoami');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 30,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('ZAKSIO_API_KEY'),
    ],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

$data = json_decode($body, true);
if ($status >= 400) {
    // application/problem+json
    throw new RuntimeException($data['code'] . ': ' . $data['detail']);
}
print_r($data);

Sample response (code 200)

{
  "key": {
    "id": "5e8d2b1c-9f3a-4b7d-8c6e-2a1b3c4d5e6f",
    "name": "Integracja sklepu",
    "prefix": "zk_live_AbCd1234",
    "scopes": [
      "products:read",
      "stock:read"
    ],
    "rateLimitPerMin": 120
  },
  "company": {
    "id": "9c1d2e3f-4a5b-4c6d-8e7f-0a1b2c3d4e5f",
    "name": "Moja Firma Sp. z o.o.",
    "nip": "5252248481"
  }
}

Possible errors: 401, 429, 500 (format described in the "Errors" section).

Products

Kartoteka produktow i uslug.

List products

GET/v1/products

Required scope: products:read

Products from the catalogue of the company assigned to the key. Cursor pagination; oldest first by default (`order=asc`), excluding inactive products.

Parameters: List products
ParameterWhereTypeDescription
limitzapytanieinteger
cursorzapytaniestring
orderzapytanieasc | desc
qzapytaniestringSzukaj w SKU i nazwie albo dokladny kod EAN
skuzapytaniestringDokladny SKU
kindzapytaniegood | service
includeInactivezapytanietrue | false

Request example

curl
curl -X GET "https://api.zaksio.pl/v1/products?limit=50&q=kawa" \
  -H "Authorization: Bearer $ZAKSIO_API_KEY"
JavaScript (Node.js)
const response = await fetch("https://api.zaksio.pl/v1/products?limit=50&q=kawa", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.ZAKSIO_API_KEY}`,
  },
});

const data = await response.json();
if (!response.ok) throw new Error(`${data.code}: ${data.detail}`); // application/problem+json
console.log(data);
Python
import os
import requests

response = requests.request(
    "GET",
    "https://api.zaksio.pl/v1/products",
    headers={
        "Authorization": f"Bearer {os.environ['ZAKSIO_API_KEY']}",
    },
    params={
        "limit": "50",
        "q": "kawa",
    },
    timeout=30,
)

if not response.ok:
    problem = response.json()  # application/problem+json
    raise RuntimeError(f"{problem['code']}: {problem['detail']}")
print(response.json())
PHP
<?php
$ch = curl_init('https://api.zaksio.pl/v1/products?limit=50&q=kawa');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 30,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('ZAKSIO_API_KEY'),
    ],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

$data = json_decode($body, true);
if ($status >= 400) {
    // application/problem+json
    throw new RuntimeException($data['code'] . ': ' . $data['detail']);
}
print_r($data);

Sample response (code 200)

{
  "data": [
    {
      "id": "3f9a1b2c-4d5e-4f60-8a7b-9c0d1e2f3a4b",
      "sku": "KAW-001",
      "name": "Kawa ziarnista 1 kg",
      "kind": "good",
      "unit": "kg",
      "ean": "5901234123457",
      "vatRate": "8",
      "defaultNetPrice": 4500,
      "tracking": "none",
      "hasExpiry": false,
      "weightG": 1000,
      "hazmat": false,
      "active": true,
      "createdAt": "2026-10-12T08:15:00.000Z"
    }
  ],
  "pagination": {
    "limit": 50,
    "hasMore": false,
    "nextCursor": null
  }
}

Possible errors: 400, 401, 403, 429, 500 (format described in the "Errors" section).

Dodanie produktu

POST/v1/products

Required scope: products:write

Adds a product to the catalogue. SKU and EAN must be unique within the company (409 `sku_or_ean_taken`). Supports `Idempotency-Key`.

Parameters: Dodanie produktu
ParameterWhereTypeDescription
Idempotency-KeyheaderstringUnikalny klucz zapisu (np. UUID). Powtorzenie zadania z tym samym kluczem zwraca zapamietana odpowiedz zamiast wykonywac zapis drugi raz (24 h).
Request fields: Dodanie produktu
FieldTypeDescription
skurequiredstringmax 64 characters
namerequiredstringmax 300 characters
kindgood | servicedefault: "good"
unitstringdefault: "szt.". max 20 characters
eanstring
vatRate0 | 5 | 8 | 23 | zw | npdefault: "23"
defaultNetPriceinteger
trackingnone | lot | serialdefault: "none"
hasExpirybooleandefault: false
weightGinteger
hazmatbooleandefault: false

Request example

curl
curl -X POST "https://api.zaksio.pl/v1/products" \
  -H "Authorization: Bearer $ZAKSIO_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 6f1c8a3e-2b4d-4e5f-9a7b-3c2d1e0f9a8b" \
  -d '{"sku":"KAW-001","name":"Kawa ziarnista 1 kg","unit":"kg","vatRate":"8","defaultNetPrice":4500,"ean":"5901234123457"}'
JavaScript (Node.js)
const response = await fetch("https://api.zaksio.pl/v1/products", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.ZAKSIO_API_KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": "6f1c8a3e-2b4d-4e5f-9a7b-3c2d1e0f9a8b",
  },
  body: JSON.stringify({
    "sku": "KAW-001",
    "name": "Kawa ziarnista 1 kg",
    "unit": "kg",
    "vatRate": "8",
    "defaultNetPrice": 4500,
    "ean": "5901234123457"
  }),
});

const data = await response.json();
if (!response.ok) throw new Error(`${data.code}: ${data.detail}`); // application/problem+json
console.log(data);
Python
import os
import requests

response = requests.request(
    "POST",
    "https://api.zaksio.pl/v1/products",
    headers={
        "Authorization": f"Bearer {os.environ['ZAKSIO_API_KEY']}",
        "Content-Type": "application/json",
        "Idempotency-Key": "6f1c8a3e-2b4d-4e5f-9a7b-3c2d1e0f9a8b",
    },
    json={
        "sku": "KAW-001",
        "name": "Kawa ziarnista 1 kg",
        "unit": "kg",
        "vatRate": "8",
        "defaultNetPrice": 4500,
        "ean": "5901234123457",
    },
    timeout=30,
)

if not response.ok:
    problem = response.json()  # application/problem+json
    raise RuntimeError(f"{problem['code']}: {problem['detail']}")
print(response.json())
PHP
<?php
$ch = curl_init('https://api.zaksio.pl/v1/products');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 30,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('ZAKSIO_API_KEY'),
        'Content-Type: application/json',
        'Idempotency-Key: 6f1c8a3e-2b4d-4e5f-9a7b-3c2d1e0f9a8b',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'sku' => 'KAW-001',
        'name' => 'Kawa ziarnista 1 kg',
        'unit' => 'kg',
        'vatRate' => '8',
        'defaultNetPrice' => 4500,
        'ean' => '5901234123457',
    ]),
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

$data = json_decode($body, true);
if ($status >= 400) {
    // application/problem+json
    throw new RuntimeException($data['code'] . ': ' . $data['detail']);
}
print_r($data);

Sample response (code 201)

{
  "id": "3f9a1b2c-4d5e-4f60-8a7b-9c0d1e2f3a4b",
  "sku": "KAW-001",
  "name": "Kawa ziarnista 1 kg",
  "kind": "good",
  "unit": "kg",
  "ean": "5901234123457",
  "vatRate": "8",
  "defaultNetPrice": 4500,
  "tracking": "none",
  "hasExpiry": false,
  "weightG": 1000,
  "hazmat": false,
  "active": true,
  "createdAt": "2026-10-12T08:15:00.000Z"
}

Possible errors: 400, 401, 402, 403, 409, 422, 429, 500 (format described in the "Errors" section).

Szczegoly produktu

GET/v1/products/{id}

Required scope: products:read

Parameters: Szczegoly produktu
ParameterWhereTypeDescription
idrequiredadresstring (uuid)

Request example

curl
curl -X GET "https://api.zaksio.pl/v1/products/3f9a1b2c-4d5e-4f60-8a7b-9c0d1e2f3a4b" \
  -H "Authorization: Bearer $ZAKSIO_API_KEY"
JavaScript (Node.js)
const response = await fetch("https://api.zaksio.pl/v1/products/3f9a1b2c-4d5e-4f60-8a7b-9c0d1e2f3a4b", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.ZAKSIO_API_KEY}`,
  },
});

const data = await response.json();
if (!response.ok) throw new Error(`${data.code}: ${data.detail}`); // application/problem+json
console.log(data);
Python
import os
import requests

response = requests.request(
    "GET",
    "https://api.zaksio.pl/v1/products/3f9a1b2c-4d5e-4f60-8a7b-9c0d1e2f3a4b",
    headers={
        "Authorization": f"Bearer {os.environ['ZAKSIO_API_KEY']}",
    },
    timeout=30,
)

if not response.ok:
    problem = response.json()  # application/problem+json
    raise RuntimeError(f"{problem['code']}: {problem['detail']}")
print(response.json())
PHP
<?php
$ch = curl_init('https://api.zaksio.pl/v1/products/3f9a1b2c-4d5e-4f60-8a7b-9c0d1e2f3a4b');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 30,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('ZAKSIO_API_KEY'),
    ],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

$data = json_decode($body, true);
if ($status >= 400) {
    // application/problem+json
    throw new RuntimeException($data['code'] . ': ' . $data['detail']);
}
print_r($data);

Sample response (code 200)

{
  "id": "3f9a1b2c-4d5e-4f60-8a7b-9c0d1e2f3a4b",
  "sku": "KAW-001",
  "name": "Kawa ziarnista 1 kg",
  "kind": "good",
  "unit": "kg",
  "ean": "5901234123457",
  "vatRate": "8",
  "defaultNetPrice": 4500,
  "tracking": "none",
  "hasExpiry": false,
  "weightG": 1000,
  "hazmat": false,
  "active": true,
  "createdAt": "2026-10-12T08:15:00.000Z"
}

Possible errors: 400, 401, 403, 404, 429, 500 (format described in the "Errors" section).

Zmiana produktu

PATCH/v1/products/{id}

Required scope: products:write

Changes only the fields provided. The kind (`kind`) cannot be changed; batch and expiry date tracking can be changed only for a product with no stock movement history.

Parameters: Zmiana produktu
ParameterWhereTypeDescription
idrequiredadresstring (uuid)
Idempotency-KeyheaderstringUnikalny klucz zapisu (np. UUID). Powtorzenie zadania z tym samym kluczem zwraca zapamietana odpowiedz zamiast wykonywac zapis drugi raz (24 h).
Request fields: Zmiana produktu
FieldTypeDescription
skustringmax 64 characters
namestringmax 300 characters
unitstringmax 20 characters
eanstring
vatRate0 | 5 | 8 | 23 | zw | np
defaultNetPriceinteger
trackingnone | lot | serial
hasExpiryboolean
weightGinteger
hazmatboolean

Request example

curl
curl -X PATCH "https://api.zaksio.pl/v1/products/3f9a1b2c-4d5e-4f60-8a7b-9c0d1e2f3a4b" \
  -H "Authorization: Bearer $ZAKSIO_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 6f1c8a3e-2b4d-4e5f-9a7b-3c2d1e0f9a8b" \
  -d '{"name":"Kawa ziarnista Arabica 1 kg","defaultNetPrice":4900}'
JavaScript (Node.js)
const response = await fetch("https://api.zaksio.pl/v1/products/3f9a1b2c-4d5e-4f60-8a7b-9c0d1e2f3a4b", {
  method: "PATCH",
  headers: {
    Authorization: `Bearer ${process.env.ZAKSIO_API_KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": "6f1c8a3e-2b4d-4e5f-9a7b-3c2d1e0f9a8b",
  },
  body: JSON.stringify({
    "name": "Kawa ziarnista Arabica 1 kg",
    "defaultNetPrice": 4900
  }),
});

const data = await response.json();
if (!response.ok) throw new Error(`${data.code}: ${data.detail}`); // application/problem+json
console.log(data);
Python
import os
import requests

response = requests.request(
    "PATCH",
    "https://api.zaksio.pl/v1/products/3f9a1b2c-4d5e-4f60-8a7b-9c0d1e2f3a4b",
    headers={
        "Authorization": f"Bearer {os.environ['ZAKSIO_API_KEY']}",
        "Content-Type": "application/json",
        "Idempotency-Key": "6f1c8a3e-2b4d-4e5f-9a7b-3c2d1e0f9a8b",
    },
    json={
        "name": "Kawa ziarnista Arabica 1 kg",
        "defaultNetPrice": 4900,
    },
    timeout=30,
)

if not response.ok:
    problem = response.json()  # application/problem+json
    raise RuntimeError(f"{problem['code']}: {problem['detail']}")
print(response.json())
PHP
<?php
$ch = curl_init('https://api.zaksio.pl/v1/products/3f9a1b2c-4d5e-4f60-8a7b-9c0d1e2f3a4b');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'PATCH',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 30,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('ZAKSIO_API_KEY'),
        'Content-Type: application/json',
        'Idempotency-Key: 6f1c8a3e-2b4d-4e5f-9a7b-3c2d1e0f9a8b',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'name' => 'Kawa ziarnista Arabica 1 kg',
        'defaultNetPrice' => 4900,
    ]),
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

$data = json_decode($body, true);
if ($status >= 400) {
    // application/problem+json
    throw new RuntimeException($data['code'] . ': ' . $data['detail']);
}
print_r($data);

Sample response (code 200)

{
  "id": "3f9a1b2c-4d5e-4f60-8a7b-9c0d1e2f3a4b",
  "sku": "KAW-001",
  "name": "Kawa ziarnista Arabica 1 kg",
  "kind": "good",
  "unit": "kg",
  "ean": "5901234123457",
  "vatRate": "8",
  "defaultNetPrice": 4900,
  "tracking": "none",
  "hasExpiry": false,
  "weightG": 1000,
  "hazmat": false,
  "active": true,
  "createdAt": "2026-10-12T08:15:00.000Z"
}

Possible errors: 400, 401, 402, 403, 404, 409, 422, 429, 500 (format described in the "Errors" section).

Stock levels

Aktualne stany magazynowe (odczyt).

Stock levels

GET/v1/stock

Required scope: stock:read

Stock level of every product that has stock movements: total and breakdown by warehouse. The list is ordered by product identifier, and the cursor points to the last returned product.

Parameters: Stock levels
ParameterWhereTypeDescription
limitzapytanieinteger
cursorzapytaniestring
warehouseIdzapytaniestring (uuid)Limit to one warehouse

Request example

curl
curl -X GET "https://api.zaksio.pl/v1/stock?limit=50" \
  -H "Authorization: Bearer $ZAKSIO_API_KEY"
JavaScript (Node.js)
const response = await fetch("https://api.zaksio.pl/v1/stock?limit=50", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.ZAKSIO_API_KEY}`,
  },
});

const data = await response.json();
if (!response.ok) throw new Error(`${data.code}: ${data.detail}`); // application/problem+json
console.log(data);
Python
import os
import requests

response = requests.request(
    "GET",
    "https://api.zaksio.pl/v1/stock",
    headers={
        "Authorization": f"Bearer {os.environ['ZAKSIO_API_KEY']}",
    },
    params={
        "limit": "50",
    },
    timeout=30,
)

if not response.ok:
    problem = response.json()  # application/problem+json
    raise RuntimeError(f"{problem['code']}: {problem['detail']}")
print(response.json())
PHP
<?php
$ch = curl_init('https://api.zaksio.pl/v1/stock?limit=50');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 30,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('ZAKSIO_API_KEY'),
    ],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

$data = json_decode($body, true);
if ($status >= 400) {
    // application/problem+json
    throw new RuntimeException($data['code'] . ': ' . $data['detail']);
}
print_r($data);

Sample response (code 200)

{
  "data": [
    {
      "productId": "3f9a1b2c-4d5e-4f60-8a7b-9c0d1e2f3a4b",
      "sku": "KAW-001",
      "name": "Kawa ziarnista 1 kg",
      "unit": "kg",
      "quantity": "42.5",
      "warehouses": [
        {
          "warehouseId": "a1b2c3d4-0000-4000-8000-000000000001",
          "warehouseCode": "MAG",
          "quantity": "40"
        },
        {
          "warehouseId": "a1b2c3d4-0000-4000-8000-000000000002",
          "warehouseCode": "SKLEP",
          "quantity": "2.5"
        }
      ]
    }
  ],
  "pagination": {
    "limit": 50,
    "hasMore": false,
    "nextCursor": null
  }
}

Possible errors: 400, 401, 403, 429, 500 (format described in the "Errors" section).

Stan jednego produktu

GET/v1/stock/{id}

Required scope: stock:read

Stock level of the product with the given identifier. A product with no stock movements has a stock level of 0.

Parameters: Stan jednego produktu
ParameterWhereTypeDescription
idrequiredadresstring (uuid)

Request example

curl
curl -X GET "https://api.zaksio.pl/v1/stock/3f9a1b2c-4d5e-4f60-8a7b-9c0d1e2f3a4b" \
  -H "Authorization: Bearer $ZAKSIO_API_KEY"
JavaScript (Node.js)
const response = await fetch("https://api.zaksio.pl/v1/stock/3f9a1b2c-4d5e-4f60-8a7b-9c0d1e2f3a4b", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.ZAKSIO_API_KEY}`,
  },
});

const data = await response.json();
if (!response.ok) throw new Error(`${data.code}: ${data.detail}`); // application/problem+json
console.log(data);
Python
import os
import requests

response = requests.request(
    "GET",
    "https://api.zaksio.pl/v1/stock/3f9a1b2c-4d5e-4f60-8a7b-9c0d1e2f3a4b",
    headers={
        "Authorization": f"Bearer {os.environ['ZAKSIO_API_KEY']}",
    },
    timeout=30,
)

if not response.ok:
    problem = response.json()  # application/problem+json
    raise RuntimeError(f"{problem['code']}: {problem['detail']}")
print(response.json())
PHP
<?php
$ch = curl_init('https://api.zaksio.pl/v1/stock/3f9a1b2c-4d5e-4f60-8a7b-9c0d1e2f3a4b');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 30,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('ZAKSIO_API_KEY'),
    ],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

$data = json_decode($body, true);
if ($status >= 400) {
    // application/problem+json
    throw new RuntimeException($data['code'] . ': ' . $data['detail']);
}
print_r($data);

Sample response (code 200)

{
  "productId": "3f9a1b2c-4d5e-4f60-8a7b-9c0d1e2f3a4b",
  "sku": "KAW-001",
  "name": "Kawa ziarnista 1 kg",
  "unit": "kg",
  "quantity": "42.5",
  "warehouses": [
    {
      "warehouseId": "a1b2c3d4-0000-4000-8000-000000000001",
      "warehouseCode": "MAG",
      "quantity": "40"
    },
    {
      "warehouseId": "a1b2c3d4-0000-4000-8000-000000000002",
      "warehouseCode": "SKLEP",
      "quantity": "2.5"
    }
  ]
}

Possible errors: 400, 401, 403, 404, 429, 500 (format described in the "Errors" section).

Counterparties

Klienci i dostawcy.

List counterparties

GET/v1/contractors

Required scope: contractors:read

Counterparties of the company assigned to the key. Cursor pagination.

Parameters: List counterparties
ParameterWhereTypeDescription
limitzapytanieinteger
cursorzapytaniestring
orderzapytanieasc | desc
qzapytaniestringSzukaj w nazwie, e-mailu i miescie
nipzapytaniestringExact NIP (digits)

Request example

curl
curl -X GET "https://api.zaksio.pl/v1/contractors?q=przyklad" \
  -H "Authorization: Bearer $ZAKSIO_API_KEY"
JavaScript (Node.js)
const response = await fetch("https://api.zaksio.pl/v1/contractors?q=przyklad", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.ZAKSIO_API_KEY}`,
  },
});

const data = await response.json();
if (!response.ok) throw new Error(`${data.code}: ${data.detail}`); // application/problem+json
console.log(data);
Python
import os
import requests

response = requests.request(
    "GET",
    "https://api.zaksio.pl/v1/contractors",
    headers={
        "Authorization": f"Bearer {os.environ['ZAKSIO_API_KEY']}",
    },
    params={
        "q": "przyklad",
    },
    timeout=30,
)

if not response.ok:
    problem = response.json()  # application/problem+json
    raise RuntimeError(f"{problem['code']}: {problem['detail']}")
print(response.json())
PHP
<?php
$ch = curl_init('https://api.zaksio.pl/v1/contractors?q=przyklad');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 30,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('ZAKSIO_API_KEY'),
    ],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

$data = json_decode($body, true);
if ($status >= 400) {
    // application/problem+json
    throw new RuntimeException($data['code'] . ': ' . $data['detail']);
}
print_r($data);

Sample response (code 200)

{
  "data": [
    {
      "id": "7d2c9e10-5a4b-4c3d-9e8f-1a2b3c4d5e6f",
      "name": "Przykladowa Sp. z o.o.",
      "nip": "5252248481",
      "country": "PL",
      "street": "Prosta 1",
      "postalCode": "00-001",
      "city": "Warszawa",
      "email": "ksiegowosc@przyklad.pl",
      "createdAt": "2026-10-12T08:20:00.000Z"
    }
  ],
  "pagination": {
    "limit": 50,
    "hasMore": false,
    "nextCursor": null
  }
}

Possible errors: 400, 401, 403, 429, 500 (format described in the "Errors" section).

Add a counterparty

POST/v1/contractors

Required scope: contractors:write

Adds a counterparty. Supports `Idempotency-Key`.

Parameters: Add a counterparty
ParameterWhereTypeDescription
Idempotency-KeyheaderstringUnikalny klucz zapisu (np. UUID). Powtorzenie zadania z tym samym kluczem zwraca zapamietana odpowiedz zamiast wykonywac zapis drugi raz (24 h).
Request fields: Add a counterparty
FieldTypeDescription
namerequiredstringmax 300 characters
nipstring
countrystringdefault: "PL"
streetstringmax 200 characters
postalCodestringmax 20 characters
citystringmax 100 characters
emailstring (email)
vatEuNumberstring
isConsumerboolean

Request example

curl
curl -X POST "https://api.zaksio.pl/v1/contractors" \
  -H "Authorization: Bearer $ZAKSIO_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 6f1c8a3e-2b4d-4e5f-9a7b-3c2d1e0f9a8b" \
  -d '{"name":"Przykladowa Sp. z o.o.","nip":"5252248481","street":"Prosta 1","postalCode":"00-001","city":"Warszawa","email":"ksiegowosc@przyklad.pl"}'
JavaScript (Node.js)
const response = await fetch("https://api.zaksio.pl/v1/contractors", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.ZAKSIO_API_KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": "6f1c8a3e-2b4d-4e5f-9a7b-3c2d1e0f9a8b",
  },
  body: JSON.stringify({
    "name": "Przykladowa Sp. z o.o.",
    "nip": "5252248481",
    "street": "Prosta 1",
    "postalCode": "00-001",
    "city": "Warszawa",
    "email": "ksiegowosc@przyklad.pl"
  }),
});

const data = await response.json();
if (!response.ok) throw new Error(`${data.code}: ${data.detail}`); // application/problem+json
console.log(data);
Python
import os
import requests

response = requests.request(
    "POST",
    "https://api.zaksio.pl/v1/contractors",
    headers={
        "Authorization": f"Bearer {os.environ['ZAKSIO_API_KEY']}",
        "Content-Type": "application/json",
        "Idempotency-Key": "6f1c8a3e-2b4d-4e5f-9a7b-3c2d1e0f9a8b",
    },
    json={
        "name": "Przykladowa Sp. z o.o.",
        "nip": "5252248481",
        "street": "Prosta 1",
        "postalCode": "00-001",
        "city": "Warszawa",
        "email": "ksiegowosc@przyklad.pl",
    },
    timeout=30,
)

if not response.ok:
    problem = response.json()  # application/problem+json
    raise RuntimeError(f"{problem['code']}: {problem['detail']}")
print(response.json())
PHP
<?php
$ch = curl_init('https://api.zaksio.pl/v1/contractors');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 30,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('ZAKSIO_API_KEY'),
        'Content-Type: application/json',
        'Idempotency-Key: 6f1c8a3e-2b4d-4e5f-9a7b-3c2d1e0f9a8b',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'name' => 'Przykladowa Sp. z o.o.',
        'nip' => '5252248481',
        'street' => 'Prosta 1',
        'postalCode' => '00-001',
        'city' => 'Warszawa',
        'email' => 'ksiegowosc@przyklad.pl',
    ]),
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

$data = json_decode($body, true);
if ($status >= 400) {
    // application/problem+json
    throw new RuntimeException($data['code'] . ': ' . $data['detail']);
}
print_r($data);

Sample response (code 201)

{
  "id": "7d2c9e10-5a4b-4c3d-9e8f-1a2b3c4d5e6f",
  "name": "Przykladowa Sp. z o.o.",
  "nip": "5252248481",
  "country": "PL",
  "street": "Prosta 1",
  "postalCode": "00-001",
  "city": "Warszawa",
  "email": "ksiegowosc@przyklad.pl",
  "createdAt": "2026-10-12T08:20:00.000Z"
}

Possible errors: 400, 401, 402, 403, 409, 422, 429, 500 (format described in the "Errors" section).

Counterparty details

GET/v1/contractors/{id}

Required scope: contractors:read

Parameters: Counterparty details
ParameterWhereTypeDescription
idrequiredadresstring (uuid)

Request example

curl
curl -X GET "https://api.zaksio.pl/v1/contractors/7d2c9e10-5a4b-4c3d-9e8f-1a2b3c4d5e6f" \
  -H "Authorization: Bearer $ZAKSIO_API_KEY"
JavaScript (Node.js)
const response = await fetch("https://api.zaksio.pl/v1/contractors/7d2c9e10-5a4b-4c3d-9e8f-1a2b3c4d5e6f", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.ZAKSIO_API_KEY}`,
  },
});

const data = await response.json();
if (!response.ok) throw new Error(`${data.code}: ${data.detail}`); // application/problem+json
console.log(data);
Python
import os
import requests

response = requests.request(
    "GET",
    "https://api.zaksio.pl/v1/contractors/7d2c9e10-5a4b-4c3d-9e8f-1a2b3c4d5e6f",
    headers={
        "Authorization": f"Bearer {os.environ['ZAKSIO_API_KEY']}",
    },
    timeout=30,
)

if not response.ok:
    problem = response.json()  # application/problem+json
    raise RuntimeError(f"{problem['code']}: {problem['detail']}")
print(response.json())
PHP
<?php
$ch = curl_init('https://api.zaksio.pl/v1/contractors/7d2c9e10-5a4b-4c3d-9e8f-1a2b3c4d5e6f');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 30,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('ZAKSIO_API_KEY'),
    ],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

$data = json_decode($body, true);
if ($status >= 400) {
    // application/problem+json
    throw new RuntimeException($data['code'] . ': ' . $data['detail']);
}
print_r($data);

Sample response (code 200)

{
  "id": "7d2c9e10-5a4b-4c3d-9e8f-1a2b3c4d5e6f",
  "name": "Przykladowa Sp. z o.o.",
  "nip": "5252248481",
  "country": "PL",
  "street": "Prosta 1",
  "postalCode": "00-001",
  "city": "Warszawa",
  "email": "ksiegowosc@przyklad.pl",
  "createdAt": "2026-10-12T08:20:00.000Z"
}

Possible errors: 400, 401, 403, 404, 429, 500 (format described in the "Errors" section).

Update a counterparty

PATCH/v1/contractors/{id}

Required scope: contractors:write

Changes only the fields provided; `null` clears the field (except name and country). Invoices already issued keep the data from the time of issue.

Parameters: Update a counterparty
ParameterWhereTypeDescription
idrequiredadresstring (uuid)
Idempotency-KeyheaderstringUnikalny klucz zapisu (np. UUID). Powtorzenie zadania z tym samym kluczem zwraca zapamietana odpowiedz zamiast wykonywac zapis drugi raz (24 h).
Request fields: Update a counterparty
FieldTypeDescription
namestringmax 300 characters
nipstring or null
countrystring
streetstring or nullmax 200 characters
postalCodestring or nullmax 20 characters
citystring or nullmax 100 characters
emailstring (email) or null
vatEuNumberstring or null
isConsumerboolean

Request example

curl
curl -X PATCH "https://api.zaksio.pl/v1/contractors/7d2c9e10-5a4b-4c3d-9e8f-1a2b3c4d5e6f" \
  -H "Authorization: Bearer $ZAKSIO_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 6f1c8a3e-2b4d-4e5f-9a7b-3c2d1e0f9a8b" \
  -d '{"city":"Krakow","email":null}'
JavaScript (Node.js)
const response = await fetch("https://api.zaksio.pl/v1/contractors/7d2c9e10-5a4b-4c3d-9e8f-1a2b3c4d5e6f", {
  method: "PATCH",
  headers: {
    Authorization: `Bearer ${process.env.ZAKSIO_API_KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": "6f1c8a3e-2b4d-4e5f-9a7b-3c2d1e0f9a8b",
  },
  body: JSON.stringify({
    "city": "Krakow",
    "email": null
  }),
});

const data = await response.json();
if (!response.ok) throw new Error(`${data.code}: ${data.detail}`); // application/problem+json
console.log(data);
Python
import os
import requests

response = requests.request(
    "PATCH",
    "https://api.zaksio.pl/v1/contractors/7d2c9e10-5a4b-4c3d-9e8f-1a2b3c4d5e6f",
    headers={
        "Authorization": f"Bearer {os.environ['ZAKSIO_API_KEY']}",
        "Content-Type": "application/json",
        "Idempotency-Key": "6f1c8a3e-2b4d-4e5f-9a7b-3c2d1e0f9a8b",
    },
    json={
        "city": "Krakow",
        "email": None,
    },
    timeout=30,
)

if not response.ok:
    problem = response.json()  # application/problem+json
    raise RuntimeError(f"{problem['code']}: {problem['detail']}")
print(response.json())
PHP
<?php
$ch = curl_init('https://api.zaksio.pl/v1/contractors/7d2c9e10-5a4b-4c3d-9e8f-1a2b3c4d5e6f');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'PATCH',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 30,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('ZAKSIO_API_KEY'),
        'Content-Type: application/json',
        'Idempotency-Key: 6f1c8a3e-2b4d-4e5f-9a7b-3c2d1e0f9a8b',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'city' => 'Krakow',
        'email' => null,
    ]),
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

$data = json_decode($body, true);
if ($status >= 400) {
    // application/problem+json
    throw new RuntimeException($data['code'] . ': ' . $data['detail']);
}
print_r($data);

Sample response (code 200)

{
  "id": "7d2c9e10-5a4b-4c3d-9e8f-1a2b3c4d5e6f",
  "name": "Przykladowa Sp. z o.o.",
  "nip": "5252248481",
  "country": "PL",
  "street": "Prosta 1",
  "postalCode": "00-001",
  "city": "Krakow",
  "email": null,
  "createdAt": "2026-10-12T08:20:00.000Z"
}

Possible errors: 400, 401, 402, 403, 404, 409, 422, 429, 500 (format described in the "Errors" section).

Delete a counterparty

DELETE/v1/contractors/{id}

Required scope: contractors:write

Deletes a counterparty that does not appear on any document (409 `contractor_in_use` otherwise).

Parameters: Delete a counterparty
ParameterWhereTypeDescription
idrequiredadresstring (uuid)
Idempotency-KeyheaderstringUnikalny klucz zapisu (np. UUID). Powtorzenie zadania z tym samym kluczem zwraca zapamietana odpowiedz zamiast wykonywac zapis drugi raz (24 h).

Request example

curl
curl -X DELETE "https://api.zaksio.pl/v1/contractors/7d2c9e10-5a4b-4c3d-9e8f-1a2b3c4d5e6f" \
  -H "Authorization: Bearer $ZAKSIO_API_KEY" \
  -H "Idempotency-Key: 6f1c8a3e-2b4d-4e5f-9a7b-3c2d1e0f9a8b"
JavaScript (Node.js)
const response = await fetch("https://api.zaksio.pl/v1/contractors/7d2c9e10-5a4b-4c3d-9e8f-1a2b3c4d5e6f", {
  method: "DELETE",
  headers: {
    Authorization: `Bearer ${process.env.ZAKSIO_API_KEY}`,
    "Idempotency-Key": "6f1c8a3e-2b4d-4e5f-9a7b-3c2d1e0f9a8b",
  },
});

if (!response.ok) {
  const problem = await response.json(); // application/problem+json
  throw new Error(`${problem.code}: ${problem.detail}`);
}
Python
import os
import requests

response = requests.request(
    "DELETE",
    "https://api.zaksio.pl/v1/contractors/7d2c9e10-5a4b-4c3d-9e8f-1a2b3c4d5e6f",
    headers={
        "Authorization": f"Bearer {os.environ['ZAKSIO_API_KEY']}",
        "Idempotency-Key": "6f1c8a3e-2b4d-4e5f-9a7b-3c2d1e0f9a8b",
    },
    timeout=30,
)

if not response.ok:
    problem = response.json()  # application/problem+json
    raise RuntimeError(f"{problem['code']}: {problem['detail']}")
PHP
<?php
$ch = curl_init('https://api.zaksio.pl/v1/contractors/7d2c9e10-5a4b-4c3d-9e8f-1a2b3c4d5e6f');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'DELETE',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 30,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('ZAKSIO_API_KEY'),
        'Idempotency-Key: 6f1c8a3e-2b4d-4e5f-9a7b-3c2d1e0f9a8b',
    ],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

$data = json_decode($body, true);
if ($status >= 400) {
    // application/problem+json
    throw new RuntimeException($data['code'] . ': ' . $data['detail']);
}

Response: code 200.

Possible errors: 400, 401, 402, 403, 404, 409, 422, 429, 500 (format described in the "Errors" section).

Invoices

Sales invoices: reading, PDF and issuing.

List sales invoices

GET/v1/invoices

Required scope: invoices:read

Invoices (and credit notes) of the company assigned to the key, without line items (details: `GET /v1/invoices/{id}`). Cursor pagination; for incremental sync use `order=asc` and remember the last cursor.

Parameters: List sales invoices
ParameterWhereTypeDescription
limitzapytanieinteger
cursorzapytaniestring
orderzapytanieasc | desc
statuszapytaniedraft | issued
contractorIdzapytaniestring (uuid)
issueDateFromzapytaniestring (date)
issueDateTozapytaniestring (date)

Request example

curl
curl -X GET "https://api.zaksio.pl/v1/invoices?status=issued&issueDateFrom=2026-10-01&limit=50" \
  -H "Authorization: Bearer $ZAKSIO_API_KEY"
JavaScript (Node.js)
const response = await fetch("https://api.zaksio.pl/v1/invoices?status=issued&issueDateFrom=2026-10-01&limit=50", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.ZAKSIO_API_KEY}`,
  },
});

const data = await response.json();
if (!response.ok) throw new Error(`${data.code}: ${data.detail}`); // application/problem+json
console.log(data);
Python
import os
import requests

response = requests.request(
    "GET",
    "https://api.zaksio.pl/v1/invoices",
    headers={
        "Authorization": f"Bearer {os.environ['ZAKSIO_API_KEY']}",
    },
    params={
        "status": "issued",
        "issueDateFrom": "2026-10-01",
        "limit": "50",
    },
    timeout=30,
)

if not response.ok:
    problem = response.json()  # application/problem+json
    raise RuntimeError(f"{problem['code']}: {problem['detail']}")
print(response.json())
PHP
<?php
$ch = curl_init('https://api.zaksio.pl/v1/invoices?status=issued&issueDateFrom=2026-10-01&limit=50');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 30,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('ZAKSIO_API_KEY'),
    ],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

$data = json_decode($body, true);
if ($status >= 400) {
    // application/problem+json
    throw new RuntimeException($data['code'] . ': ' . $data['detail']);
}
print_r($data);

Sample response (code 200)

{
  "data": [
    {
      "id": "0b6f7a3e-1c2d-4e5f-8a9b-0c1d2e3f4a5b",
      "number": "FV/001/10/2026",
      "kind": "sales",
      "status": "issued",
      "contractorId": "7d2c9e10-5a4b-4c3d-9e8f-1a2b3c4d5e6f",
      "issueDate": "2026-10-12",
      "saleDate": "2026-10-12",
      "dueDate": "2026-10-26",
      "currency": "PLN",
      "paymentMethod": "przelew",
      "bankAccount": null,
      "notes": null,
      "vatExemptionBasis": null,
      "netTotal": 25000,
      "vatTotal": 5000,
      "grossTotal": 30000,
      "paidTotal": 0,
      "paidAt": null,
      "correctsInvoiceId": null,
      "ksefNumber": null,
      "ksefStatus": null,
      "issuedAt": "2026-10-12T09:30:00.000Z",
      "createdAt": "2026-10-12T09:30:00.000Z"
    }
  ],
  "pagination": {
    "limit": 50,
    "hasMore": false,
    "nextCursor": null
  }
}

Possible errors: 400, 401, 403, 429, 500 (format described in the "Errors" section).

Issue an invoice

POST/v1/invoices

Required scope: invoices:write

Creates a sales invoice and (by default) issues it immediately: gapless numbering, posting and VAT summary as in the app. Amounts in grosze, quantity as a decimal string. Issuing and saving are a single transaction: on error no draft is left behind. **Always provide `Idempotency-Key`** so that a retry after a network error does not issue the invoice a second time.

Parameters: Issue an invoice
ParameterWhereTypeDescription
Idempotency-KeyheaderstringUnikalny klucz zapisu (np. UUID). Powtorzenie zadania z tym samym kluczem zwraca zapamietana odpowiedz zamiast wykonywac zapis drugi raz (24 h).
Request fields: Issue an invoice
FieldTypeDescription
contractorIdrequiredstring (uuid)
issueDaterequiredstring (date)
saleDaterequiredstring (date)
dueDatestring (date)
currencystring
taxPointDatestring (date)
vatScenariodomestic | domestic_reverse_charge | wdt | export_goods | services_eu_b2b | services_non_eu | oss_union | ioss
vatOptionsobject
buyerVatIdstring
paymentMethodstringmax 50 characters
bankAccountstringmax 40 characters
notesstringmax 2000 characters
vatExemptionBasisstringmax 240 characters
linesrequiredobject[]
issuebooleantrue (default): the invoice is issued immediately (number, posting); false: saves a draft for later issue. default: true

Request example

curl
curl -X POST "https://api.zaksio.pl/v1/invoices" \
  -H "Authorization: Bearer $ZAKSIO_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 6f1c8a3e-2b4d-4e5f-9a7b-3c2d1e0f9a8b" \
  -d '{"contractorId":"7d2c9e10-5a4b-4c3d-9e8f-1a2b3c4d5e6f","issueDate":"2026-10-12","saleDate":"2026-10-12","dueDate":"2026-10-26","paymentMethod":"przelew","lines":[{"name":"Usluga A","quantity":"2","unitNetPrice":10000,"vatRate":"23"},{"name":"Usluga B","quantity":"1","unitNetPrice":5000,"vatRate":"8"}]}'
JavaScript (Node.js)
const response = await fetch("https://api.zaksio.pl/v1/invoices", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.ZAKSIO_API_KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": "6f1c8a3e-2b4d-4e5f-9a7b-3c2d1e0f9a8b",
  },
  body: JSON.stringify({
    "contractorId": "7d2c9e10-5a4b-4c3d-9e8f-1a2b3c4d5e6f",
    "issueDate": "2026-10-12",
    "saleDate": "2026-10-12",
    "dueDate": "2026-10-26",
    "paymentMethod": "przelew",
    "lines": [
      {
        "name": "Usluga A",
        "quantity": "2",
        "unitNetPrice": 10000,
        "vatRate": "23"
      },
      {
        "name": "Usluga B",
        "quantity": "1",
        "unitNetPrice": 5000,
        "vatRate": "8"
      }
    ]
  }),
});

const data = await response.json();
if (!response.ok) throw new Error(`${data.code}: ${data.detail}`); // application/problem+json
console.log(data);
Python
import os
import requests

response = requests.request(
    "POST",
    "https://api.zaksio.pl/v1/invoices",
    headers={
        "Authorization": f"Bearer {os.environ['ZAKSIO_API_KEY']}",
        "Content-Type": "application/json",
        "Idempotency-Key": "6f1c8a3e-2b4d-4e5f-9a7b-3c2d1e0f9a8b",
    },
    json={
        "contractorId": "7d2c9e10-5a4b-4c3d-9e8f-1a2b3c4d5e6f",
        "issueDate": "2026-10-12",
        "saleDate": "2026-10-12",
        "dueDate": "2026-10-26",
        "paymentMethod": "przelew",
        "lines": [
            {
                "name": "Usluga A",
                "quantity": "2",
                "unitNetPrice": 10000,
                "vatRate": "23",
            },
            {
                "name": "Usluga B",
                "quantity": "1",
                "unitNetPrice": 5000,
                "vatRate": "8",
            },
        ],
    },
    timeout=30,
)

if not response.ok:
    problem = response.json()  # application/problem+json
    raise RuntimeError(f"{problem['code']}: {problem['detail']}")
print(response.json())
PHP
<?php
$ch = curl_init('https://api.zaksio.pl/v1/invoices');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 30,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('ZAKSIO_API_KEY'),
        'Content-Type: application/json',
        'Idempotency-Key: 6f1c8a3e-2b4d-4e5f-9a7b-3c2d1e0f9a8b',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'contractorId' => '7d2c9e10-5a4b-4c3d-9e8f-1a2b3c4d5e6f',
        'issueDate' => '2026-10-12',
        'saleDate' => '2026-10-12',
        'dueDate' => '2026-10-26',
        'paymentMethod' => 'przelew',
        'lines' => [
            [
                'name' => 'Usluga A',
                'quantity' => '2',
                'unitNetPrice' => 10000,
                'vatRate' => '23',
            ],
            [
                'name' => 'Usluga B',
                'quantity' => '1',
                'unitNetPrice' => 5000,
                'vatRate' => '8',
            ],
        ],
    ]),
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

$data = json_decode($body, true);
if ($status >= 400) {
    // application/problem+json
    throw new RuntimeException($data['code'] . ': ' . $data['detail']);
}
print_r($data);

Sample response (code 201)

{
  "id": "0b6f7a3e-1c2d-4e5f-8a9b-0c1d2e3f4a5b",
  "number": "FV/001/10/2026",
  "kind": "sales",
  "status": "issued",
  "contractorId": "7d2c9e10-5a4b-4c3d-9e8f-1a2b3c4d5e6f",
  "issueDate": "2026-10-12",
  "saleDate": "2026-10-12",
  "dueDate": "2026-10-26",
  "currency": "PLN",
  "paymentMethod": "przelew",
  "bankAccount": null,
  "notes": null,
  "vatExemptionBasis": null,
  "netTotal": 25000,
  "vatTotal": 5000,
  "grossTotal": 30000,
  "paidTotal": 0,
  "paidAt": null,
  "correctsInvoiceId": null,
  "ksefNumber": null,
  "ksefStatus": null,
  "issuedAt": "2026-10-12T09:30:00.000Z",
  "createdAt": "2026-10-12T09:30:00.000Z",
  "lines": [
    {
      "position": 1,
      "name": "Usluga A",
      "unit": "szt.",
      "quantity": "2",
      "unitNetPrice": 10000,
      "vatRate": "23",
      "net": 20000,
      "productId": null
    },
    {
      "position": 2,
      "name": "Usluga B",
      "unit": "szt.",
      "quantity": "1",
      "unitNetPrice": 5000,
      "vatRate": "8",
      "net": 5000,
      "productId": null
    }
  ],
  "vatSummary": [
    {
      "vatRate": "23",
      "net": 20000,
      "vat": 4600,
      "gross": 24600
    },
    {
      "vatRate": "8",
      "net": 5000,
      "vat": 400,
      "gross": 5400
    }
  ]
}

Possible errors: 400, 401, 402, 403, 409, 422, 429, 500 (format described in the "Errors" section).

Invoice details

GET/v1/invoices/{id}

Required scope: invoices:read

Invoice with line items and a VAT summary.

Parameters: Invoice details
ParameterWhereTypeDescription
idrequiredadresstring (uuid)

Request example

curl
curl -X GET "https://api.zaksio.pl/v1/invoices/0b6f7a3e-1c2d-4e5f-8a9b-0c1d2e3f4a5b" \
  -H "Authorization: Bearer $ZAKSIO_API_KEY"
JavaScript (Node.js)
const response = await fetch("https://api.zaksio.pl/v1/invoices/0b6f7a3e-1c2d-4e5f-8a9b-0c1d2e3f4a5b", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.ZAKSIO_API_KEY}`,
  },
});

const data = await response.json();
if (!response.ok) throw new Error(`${data.code}: ${data.detail}`); // application/problem+json
console.log(data);
Python
import os
import requests

response = requests.request(
    "GET",
    "https://api.zaksio.pl/v1/invoices/0b6f7a3e-1c2d-4e5f-8a9b-0c1d2e3f4a5b",
    headers={
        "Authorization": f"Bearer {os.environ['ZAKSIO_API_KEY']}",
    },
    timeout=30,
)

if not response.ok:
    problem = response.json()  # application/problem+json
    raise RuntimeError(f"{problem['code']}: {problem['detail']}")
print(response.json())
PHP
<?php
$ch = curl_init('https://api.zaksio.pl/v1/invoices/0b6f7a3e-1c2d-4e5f-8a9b-0c1d2e3f4a5b');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 30,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('ZAKSIO_API_KEY'),
    ],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

$data = json_decode($body, true);
if ($status >= 400) {
    // application/problem+json
    throw new RuntimeException($data['code'] . ': ' . $data['detail']);
}
print_r($data);

Sample response (code 200)

{
  "id": "0b6f7a3e-1c2d-4e5f-8a9b-0c1d2e3f4a5b",
  "number": "FV/001/10/2026",
  "kind": "sales",
  "status": "issued",
  "contractorId": "7d2c9e10-5a4b-4c3d-9e8f-1a2b3c4d5e6f",
  "issueDate": "2026-10-12",
  "saleDate": "2026-10-12",
  "dueDate": "2026-10-26",
  "currency": "PLN",
  "paymentMethod": "przelew",
  "bankAccount": null,
  "notes": null,
  "vatExemptionBasis": null,
  "netTotal": 25000,
  "vatTotal": 5000,
  "grossTotal": 30000,
  "paidTotal": 0,
  "paidAt": null,
  "correctsInvoiceId": null,
  "ksefNumber": null,
  "ksefStatus": null,
  "issuedAt": "2026-10-12T09:30:00.000Z",
  "createdAt": "2026-10-12T09:30:00.000Z",
  "lines": [
    {
      "position": 1,
      "name": "Usluga A",
      "unit": "szt.",
      "quantity": "2",
      "unitNetPrice": 10000,
      "vatRate": "23",
      "net": 20000,
      "productId": null
    },
    {
      "position": 2,
      "name": "Usluga B",
      "unit": "szt.",
      "quantity": "1",
      "unitNetPrice": 5000,
      "vatRate": "8",
      "net": 5000,
      "productId": null
    }
  ],
  "vatSummary": [
    {
      "vatRate": "23",
      "net": 20000,
      "vat": 4600,
      "gross": 24600
    },
    {
      "vatRate": "8",
      "net": 5000,
      "vat": 400,
      "gross": 5400
    }
  ]
}

Possible errors: 400, 401, 403, 404, 429, 500 (format described in the "Errors" section).

Invoice PDF

GET/v1/invoices/{id}/pdf

Required scope: invoices:read

Invoice PDF file in the same layout as in the app (`Content-Type: application/pdf`). Download the PDF of an issued invoice: a draft has no number yet.

Parameters: Invoice PDF
ParameterWhereTypeDescription
idrequiredadresstring (uuid)

Request example

curl
curl -X GET "https://api.zaksio.pl/v1/invoices/0b6f7a3e-1c2d-4e5f-8a9b-0c1d2e3f4a5b/pdf" \
  -H "Authorization: Bearer $ZAKSIO_API_KEY" \
  -o faktura.pdf
JavaScript (Node.js)
import { writeFileSync } from "node:fs";

const response = await fetch("https://api.zaksio.pl/v1/invoices/0b6f7a3e-1c2d-4e5f-8a9b-0c1d2e3f4a5b/pdf", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.ZAKSIO_API_KEY}`,
  },
});

if (!response.ok) throw new Error(`${response.status}: ${await response.text()}`);
writeFileSync("faktura.pdf", Buffer.from(await response.arrayBuffer()));
Python
import os
import requests

response = requests.request(
    "GET",
    "https://api.zaksio.pl/v1/invoices/0b6f7a3e-1c2d-4e5f-8a9b-0c1d2e3f4a5b/pdf",
    headers={
        "Authorization": f"Bearer {os.environ['ZAKSIO_API_KEY']}",
    },
    timeout=30,
)

if not response.ok:
    problem = response.json()  # application/problem+json
    raise RuntimeError(f"{problem['code']}: {problem['detail']}")
open("faktura.pdf", "wb").write(response.content)
PHP
<?php
$ch = curl_init('https://api.zaksio.pl/v1/invoices/0b6f7a3e-1c2d-4e5f-8a9b-0c1d2e3f4a5b/pdf');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 30,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('ZAKSIO_API_KEY'),
    ],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($status >= 400) { throw new RuntimeException($body); }
file_put_contents('faktura.pdf', $body);

Response: code 200.

Possible errors: 400, 401, 403, 404, 429, 500 (format described in the "Errors" section).

Issue a draft invoice

POST/v1/invoices/{id}/issue

Required scope: invoices:write

Issues an invoice previously saved as a draft (from the API or the app). An issued invoice cannot be issued again (409 `invoice_not_draft`). Supports `Idempotency-Key`.

Parameters: Issue a draft invoice
ParameterWhereTypeDescription
idrequiredadresstring (uuid)
Idempotency-KeyheaderstringUnikalny klucz zapisu (np. UUID). Powtorzenie zadania z tym samym kluczem zwraca zapamietana odpowiedz zamiast wykonywac zapis drugi raz (24 h).

Request example

curl
curl -X POST "https://api.zaksio.pl/v1/invoices/0b6f7a3e-1c2d-4e5f-8a9b-0c1d2e3f4a5b/issue" \
  -H "Authorization: Bearer $ZAKSIO_API_KEY" \
  -H "Idempotency-Key: 6f1c8a3e-2b4d-4e5f-9a7b-3c2d1e0f9a8b"
JavaScript (Node.js)
const response = await fetch("https://api.zaksio.pl/v1/invoices/0b6f7a3e-1c2d-4e5f-8a9b-0c1d2e3f4a5b/issue", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.ZAKSIO_API_KEY}`,
    "Idempotency-Key": "6f1c8a3e-2b4d-4e5f-9a7b-3c2d1e0f9a8b",
  },
});

const data = await response.json();
if (!response.ok) throw new Error(`${data.code}: ${data.detail}`); // application/problem+json
console.log(data);
Python
import os
import requests

response = requests.request(
    "POST",
    "https://api.zaksio.pl/v1/invoices/0b6f7a3e-1c2d-4e5f-8a9b-0c1d2e3f4a5b/issue",
    headers={
        "Authorization": f"Bearer {os.environ['ZAKSIO_API_KEY']}",
        "Idempotency-Key": "6f1c8a3e-2b4d-4e5f-9a7b-3c2d1e0f9a8b",
    },
    timeout=30,
)

if not response.ok:
    problem = response.json()  # application/problem+json
    raise RuntimeError(f"{problem['code']}: {problem['detail']}")
print(response.json())
PHP
<?php
$ch = curl_init('https://api.zaksio.pl/v1/invoices/0b6f7a3e-1c2d-4e5f-8a9b-0c1d2e3f4a5b/issue');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 30,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('ZAKSIO_API_KEY'),
        'Idempotency-Key: 6f1c8a3e-2b4d-4e5f-9a7b-3c2d1e0f9a8b',
    ],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

$data = json_decode($body, true);
if ($status >= 400) {
    // application/problem+json
    throw new RuntimeException($data['code'] . ': ' . $data['detail']);
}
print_r($data);

Sample response (code 200)

{
  "id": "0b6f7a3e-1c2d-4e5f-8a9b-0c1d2e3f4a5b",
  "number": "FV/001/10/2026",
  "kind": "sales",
  "status": "issued",
  "contractorId": "7d2c9e10-5a4b-4c3d-9e8f-1a2b3c4d5e6f",
  "issueDate": "2026-10-12",
  "saleDate": "2026-10-12",
  "dueDate": "2026-10-26",
  "currency": "PLN",
  "paymentMethod": "przelew",
  "bankAccount": null,
  "notes": null,
  "vatExemptionBasis": null,
  "netTotal": 25000,
  "vatTotal": 5000,
  "grossTotal": 30000,
  "paidTotal": 0,
  "paidAt": null,
  "correctsInvoiceId": null,
  "ksefNumber": null,
  "ksefStatus": null,
  "issuedAt": "2026-10-12T09:30:00.000Z",
  "createdAt": "2026-10-12T09:30:00.000Z",
  "lines": [
    {
      "position": 1,
      "name": "Usluga A",
      "unit": "szt.",
      "quantity": "2",
      "unitNetPrice": 10000,
      "vatRate": "23",
      "net": 20000,
      "productId": null
    },
    {
      "position": 2,
      "name": "Usluga B",
      "unit": "szt.",
      "quantity": "1",
      "unitNetPrice": 5000,
      "vatRate": "8",
      "net": 5000,
      "productId": null
    }
  ],
  "vatSummary": [
    {
      "vatRate": "23",
      "net": 20000,
      "vat": 4600,
      "gross": 24600
    },
    {
      "vatRate": "8",
      "net": 5000,
      "vat": 400,
      "gross": 5400
    }
  ]
}

Possible errors: 400, 401, 402, 403, 404, 409, 422, 429, 500 (format described in the "Errors" section).

Webhooks

A webhook is a POST request that Zaksio sends to your address (https, public) when an event occurs. You add subscriptions in the app (Settings, "API and integrations", "Webhooks" tab): you enter the address, choose the events, optionally a single company, and save the signing secret (shown only once).

Envelope and headers

The body is a JSON object with the fields id (event identifier, constant across retries), type, apiVersion, createdAt, companyId i data (data in the same shape as in API responses). The request has the headers Zaksio-Event, Zaksio-Event-Id, Zaksio-Delivery i Zaksio-Signature.

Webhook events
EventWhen
invoice.issuedInvoice issued. A sales invoice (or credit note) was issued and posted.
invoice.paidInvoice paid. Na fakturze zarejestrowano pierwsza wplate (pole paidAt ma wartosc).
contractor.createdCounterparty added. A counterparty was added (from the app, the API or an import).
contractor.updatedCounterparty changed. Counterparty details were changed.
product.createdProduct added. A product was added to the catalogue.
product.updatedProduct changed. Product details in the catalogue were changed (including deactivation).
product.stock_changedStan produktu zmieniony. Zmienil sie stan magazynowy produktu. Kilka ruchow zapisanych w krotkim czasie (zanim zdarzenie zostanie rozeslane) zlewa sie w jedno zdarzenie z aktualnym stanem.
{
  "id": "b9f1c2d3-4e5f-4a6b-8c7d-0e1f2a3b4c5d",
  "type": "invoice.issued",
  "apiVersion": "v1",
  "createdAt": "2026-10-12T09:30:00.000Z",
  "companyId": "9c1d2e3f-4a5b-4c6d-8e7f-0a1b2c3d4e5f",
  "data": {
    "invoice": {
      "id": "0b6f7a3e-1c2d-4e5f-8a9b-0c1d2e3f4a5b",
      "number": "FV/001/10/2026",
      "kind": "sales",
      "status": "issued",
      "grossTotal": 12300,
      "currency": "PLN"
    }
  }
}

You can find sample data for the other events in the OpenAPI file (field x-webhook-events). The "Test" button sends the event webhook.test with sample content so you can check the address and signature verification.

Signature verification

Header Zaksio-Signature has the form t=<unix>,v1=<hex>. The signature is an HMAC-SHA256 of the subscription secret over the text <t>.<raw request body>. Verify the signature on the raw body (not on re-serialised JSON), compare it in constant time and reject requests with a timestamp t older than 5 minutes. While the secret is being rotated, the header may have several values v1=: it is enough that one matches.

Checking the signature

JavaScript (Node.js)
import { createHmac, timingSafeEqual } from "node:crypto";

// rawBody: surowa tresc zadania (Buffer lub string), NIE ponownie zserializowany JSON
export function verifyZaksioSignature(secret, header, rawBody, toleranceSeconds = 300) {
  const parts = header.split(",").map((p) => p.trim());
  const timestamp = Number(parts.find((p) => p.startsWith("t="))?.slice(2));
  if (!Number.isInteger(timestamp)) return false;
  if (Math.abs(Date.now() / 1000 - timestamp) > toleranceSeconds) return false;
  const expected = createHmac("sha256", secret).update(`${timestamp}.${rawBody}`).digest();
  return parts
    .filter((p) => p.startsWith("v1="))
    .some((p) => {
      const given = Buffer.from(p.slice(3), "hex");
      return given.length === expected.length && timingSafeEqual(given, expected);
    });
}
Python
import hashlib
import hmac
import time


def verify_zaksio_signature(secret: str, header: str, raw_body: bytes, tolerance: int = 300) -> bool:
    parts = [p.strip() for p in header.split(",")]
    try:
        timestamp = int(next(p[2:] for p in parts if p.startswith("t=")))
    except (StopIteration, ValueError):
        return False
    if abs(time.time() - timestamp) > tolerance:
        return False
    signed = f"{timestamp}.".encode() + raw_body
    expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
    return any(
        hmac.compare_digest(p[3:], expected) for p in parts if p.startswith("v1=")
    )
PHP
<?php
function verifyZaksioSignature(string $secret, string $header, string $rawBody, int $tolerance = 300): bool
{
    $timestamp = null;
    $signatures = [];
    foreach (array_map('trim', explode(',', $header)) as $part) {
        if (str_starts_with($part, 't=')) {
            $timestamp = (int) substr($part, 2);
        } elseif (str_starts_with($part, 'v1=')) {
            $signatures[] = substr($part, 3);
        }
    }
    if ($timestamp === null || abs(time() - $timestamp) > $tolerance) {
        return false;
    }
    $expected = hash_hmac('sha256', $timestamp . '.' . $rawBody, $secret);
    foreach ($signatures as $signature) {
        if (hash_equals($expected, $signature)) {
            return true;
        }
    }
    return false;
}

// $rawBody = file_get_contents('php://input');
// $header = $_SERVER['HTTP_ZAKSIO_SIGNATURE'] ?? '';
Bash (openssl)
#!/usr/bin/env bash
# Sprawdzenie podpisu z wiersza polecen (do diagnozy): SECRET, HEADER i plik z surowa trescia zadania
SECRET="whsec_..."
HEADER="t=1760261400,v1=..."   # naglowek Zaksio-Signature
BODY_FILE="body.json"          # surowa tresc zadania, bajt w bajt

T=$(echo "$HEADER" | tr ',' '\n' | sed -n 's/^t=//p')
EXPECTED=$( (printf '%s.' "$T"; cat "$BODY_FILE") | openssl dgst -sha256 -hmac "$SECRET" -hex | sed 's/^.* //')
if echo "$HEADER" | tr ',' '\n' | grep -qx "v1=$EXPECTED"; then echo "podpis poprawny"; else echo "podpis NIEPOPRAWNY"; fi
# pamietaj tez o sprawdzeniu, ze t= nie jest starsze niz 5 minut

Delivery and retries

  • Delivery is successful after a 2xx response within 10 seconds. Redirects are not supported (we treat them as an error).
  • After an error we retry after 1 minute, 5 minutes, 30 minutes, 2, 6 and 12 hours (7 attempts in total). After that the delivery is marked "failed"; you can retry it manually in the delivery log.
  • After 25 consecutive failed attempts, the subscription is disabled automatically.
  • Delivery is "at least once": the same event may arrive more than once, and the order is not guaranteed. Identify events by the field id and respond quickly (2xx), and do heavy processing asynchronously.
  • Product stock events may be merged: several changes saved within a short time produce a single event with the current stock level.
  • Internal, local and private addresses are blocked (SSRF protection), also after DNS name resolution.

Change history

1.0.0: First version of API v1 (12 October 2026)

  • API key authentication (Authorization: Bearer zk_live_...), scopes, IP address list, expiry, rotation with a transition period and revocation.
  • Products: list, details, adding and partial update.
  • Stock levels: list and product details (total and breakdown by warehouse).
  • Counterparties: list, details, adding, partial update and deletion.
  • Sales invoices: list, details, PDF, issuing (immediately or from a draft) with numbering and posting as in the application.
  • Idempotency-Key on writes, cursor pagination, errors in RFC 9457 format and RateLimit-* limit headers.
  • Webhooks: invoice issued and paid, counterparty and product added or changed, product stock change; HMAC-SHA256 signature with a timestamp, retries with increasing intervals, delivery log and connection test.