Fakturnia Developers Quickstart API reference Polski

Authentication

The Fakturnia API authenticates with OAuth 2.0 client credentials. Your application exchanges a client_id / client_secret pair for a short-lived bearer token (JWT), then sends that token on every request.

The REST API requires the MAX plan. API accounts, every /external/v1 endpoint and webhooks are a MAX plan module. Without an active plan the token endpoint answers 403 with error: "access_denied" and error_code: "SUBSCRIPTION_REQUIRED", and a call with a token issued earlier ends with 403 SUBSCRIPTION_REQUIRED. When the plan lapses the integration stops until it is renewed; no webhooks are delivered in the meantime. Employee accounts and in-app permissions play no part: a token always acts as the account owner.

Getting credentials

Create an API application in Inne → Integracje → REST API (the app UI is in Polish). Each application has:

  • a client_id (public identifier, e.g. client_5c62e069…),
  • a client_secret (secret, store it server-side only),
  • a set of scopes that bound what the application may do.

You can run multiple applications (e.g. one per integration) and revoke any of them independently. Revocation is immediate: tokens issued earlier get 401 UNAUTHORIZED on their next call, not only when they expire. You can also restrict an application to a list of IP addresses; a call from any other address ends with 403 FORBIDDEN.

API credentials are tied to their environment. Credentials from the test environment (https://sandbox.app.fakturnia.pl) work only against https://sandbox.api.fakturnia.pl, and production credentials only against https://prod.api.fakturnia.pl. Start in the sandbox and switch to production only after the integration has been tested.

Requesting a token

curl -s https://prod.api.fakturnia.pl/v1/oauth/token \
  -H "Content-Type: application/json" \
  -d '{ "grant_type": "client_credentials", "client_id": "...", "client_secret": "..." }'
{ "access_token": "eyJ…", "token_type": "Bearer", "expires_in": 3600, "scope": "documents:create documents:read" }

Tokens are valid for expires_in seconds. Cache and reuse the token until it is close to expiry rather than requesting a new one per call. After expiry the API answers 401 TOKEN_EXPIRED: get a new token and retry.

The token endpoint answers as OAuth 2.0 prescribes: 400 invalid_request / unsupported_grant_type for a malformed request, 401 invalid_client for bad credentials (without revealing whether the client_id exists), 403 access_denied with error_code: "SUBSCRIPTION_REQUIRED" without the MAX plan, and 403 access_denied without an error_code when the request comes from an IP address outside the application's allowlist.

let cached: { token: string; exp: number } | null = null;

async function getToken(): Promise<string> {
  const now = Date.now();
  if (cached && cached.exp - now > 60_000) return cached.token;
  const res = await fetch('https://prod.api.fakturnia.pl/v1/oauth/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      grant_type: 'client_credentials',
      client_id: process.env.FAKTURNIA_CLIENT_ID,
      client_secret: process.env.FAKTURNIA_CLIENT_SECRET,
    }),
  });
  const json = await res.json();
  cached = { token: json.access_token, exp: now + json.expires_in * 1000 };
  return cached.token;
}

Using the token

Authorization: Bearer <access_token>

A missing or invalid token returns 401 with code UNAUTHORIZED, an expired one 401 TOKEN_EXPIRED. A valid token that lacks the required scope returns 403 with code NO_PERMISSIONS, and errors[0] names the missing scope. Order of checks: token, API account status, IP address (allowlist), MAX plan, then the scope.

Scopes

Each endpoint declares the scope it requires. Request only the scopes your integration needs.

Resource Scopes
Documents documents:browse · documents:read · documents:create · documents:update · documents:delete · documents:download · documents:send-to-ksef · documents:update-payment-status · documents:update-external-reference
Clients clients:browse · clients:read · clients:create · clients:update · clients:delete
Products products:browse · products:read · products:create · products:update · products:delete
Warehouse warehouse:browse · warehouse:create · warehouse:update · warehouse:delete · warehouse:stock:read · warehouse:adjust · warehouse:wz:create · warehouse:documents:create
Employees employees:browse · employees:create · employees:update · employees:delete
Recurring documents recurring:browse · recurring:read · recurring:toggle
KSeF ksef:read · ksef:configure · ksef:test
Settings settings:read · settings:update
Webhooks webhooks:read · webhooks:create · webhooks:update · webhooks:delete
Email accounts email-accounts:browse · email-accounts:read
Activity activity:read
Company gus:company:read

The exact scope for any endpoint is shown in the API reference.

Security checklist

  • Keep client_secret server-side. Never embed it in browsers, mobile apps, or public repos.
  • Store tokens in memory or a short-lived cache. They expire, so persisting them long-term adds risk without benefit.
  • Use HTTPS only. The API rejects plaintext.
  • Rotate a secret immediately if it leaks (revoke the application and create a new one); revocation also cuts off tokens already issued.
  • Restrict the source IP addresses of an application when the integration runs from a server with a fixed address.
  • Scope down: an integration that only reads should not hold documents:create.

Checking a token

GET /auth/me returns the identity and granted scopes behind the current token. Use it after deploying an integration to confirm the token works and carries the right permissions, or to debug which application a token belongs to. The endpoint only requires a valid token, no extra scope.

curl -s https://prod.api.fakturnia.pl/external/v1/auth/me \
  -H "Authorization: Bearer $TOKEN"
{
  "success": true,
  "data": {
    "clientId": "507f1f77bcf86cd799439011",
    "name": "Shop integration",
    "scopes": ["documents:read", "clients:browse"],
    "userId": "507f1f77bcf86cd799439012"
  }
}