eFakturuj / API Docs
eFakturuj Guides

Authentication

eFakturuj's API supports three authentication modes. Pick the one that matches how your code is calling us.

Decision matrix

ScenarioUse
Flutter / iOS / Android mobile appBearerJWT
Browser SPA (your own users signing in)BearerJWT
Your accounting backend → eFakturujApiKey
Third-party app acting on behalf of an eFakturuj userOAuth2 (client credentials)

BearerJWT — interactive sign-in

Short-lived 15-minute RS256 JWT issued by POST /auth/login (or POST /auth/refresh). Send it on every request as Authorization: Bearer <jwt>. Use this for anything where a real human is signing in.

Login

curl -X POST https://api.efakturuj.sk/api/v1/auth/login \
  -H 'Content-Type: application/json' \
  -d '{"email": "you@example.sk", "password": "..."}'

Response (LoginResponse):

{
  "access_token": "eyJhbGciOi...",
  "refresh_token": "eyJhbGciOi...",
  "expires_in": 900,
  "user": { "id": "...", "email": "...", "name": "...", "is_platform_admin": false }
}

Dart:

final res = await dio.post('/auth/login', data: {
  'email': email,
  'password': password,
});
final accessToken = res.data['access_token'] as String;
final refreshToken = res.data['refresh_token'] as String;

Python:

res = httpx.post(
    "https://api.efakturuj.sk/api/v1/auth/login",
    json={"email": email, "password": password},
)
res.raise_for_status()
tokens = res.json()

Refresh

When you get a 401 because the access token expired, exchange the refresh token for a new pair:

curl -X POST https://api.efakturuj.sk/api/v1/auth/refresh \
  -H 'Content-Type: application/json' \
  -d '{"refresh_token": "eyJhbGciOi..."}'

POST /auth/refresh rotates the refresh token — the response contains both a new access_token and a new refresh_token. Discard the old refresh token immediately and persist the new one.

ApiKey — server-to-server

Long-lived API key created at POST /api-keys from the dashboard (requires the admin role). Send it as X-Api-Key: <key>. Use this for headless backends, cron jobs, ETL pipelines.

curl -X POST https://api.efakturuj.sk/api/v1/api-keys \
  -H 'Authorization: Bearer <admin-jwt>' \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "Production backend",
    "scopes": ["invoices:read", "invoices:write", "invoices:send"],
    "expires_in_days": 365
  }'

The response includes the plaintext api_key exactly once. Store it immediately — only the SHA-256 hash is persisted server-side and the value cannot be recovered.

Available scopes: invoices:read, invoices:write, invoices:send, validate:only. Keys are formatted efk_live_<32-char-suffix>.

Use it on subsequent requests:

curl https://api.efakturuj.sk/api/v1/invoices \
  -H 'X-Api-Key: efk_live_...'

The same key may also be sent as a Bearer token — the server recognises any value beginning with efk_live_ or efk_test_ as an API key.

OAuth2 — third-party integrations

OAuth2 client-credentials grant (RFC 6749 §4.4) for headless apps acting on behalf of an organisation. The dashboard provisions a client_id + client_secret pair via POST /oauth2/clients; both values are returned exactly once and only the SHA-256 hash of the secret is stored.

Note. Today only the client_credentials grant is supported. The authorization-code redirect flow described in the OpenAPI security scheme is reserved for a future release.

Exchange credentials for a 15-minute JWT:

curl -X POST https://api.efakturuj.sk/api/v1/oauth2/token \
  -H 'Content-Type: application/json' \
  -d '{
    "grant_type": "client_credentials",
    "client_id": "efk_client_...",
    "client_secret": "efk_secret_..."
  }'

Response:

{
  "access_token": "eyJhbGciOi...",
  "token_type": "Bearer",
  "expires_in": 900
}

Send the resulting JWT as Authorization: Bearer <token>. No refresh token is issued — call /oauth2/token again with the same static credentials when the access token expires.

Token lifetimes

TokenTTLSource
Access token (login)15 minjwt_access_token_expire_minutes
Refresh token (login)30 daysjwt_refresh_token_expire_days
OAuth2 access token15 minhard-coded in /oauth2/token
API keyuntil revoked, or expires_in_days if set (1–365)ApiKey.expires_at

All JWTs are RS256-signed; verify with the public key from the JWKS endpoint if you need offline validation.

Where to store credentials

  • Mobile (iOS / Android / Flutter) — Keychain on iOS, EncryptedSharedPreferences / Keystore on Android. Never write tokens to logs or analytics.
  • Browser SPA — keep the access token in memory and the refresh token in an HTTP-only cookie issued by your own backend. Avoid localStorage for refresh tokens.
  • Server-to-server — API key in an environment variable or secret manager (AWS Secrets Manager, HashiCorp Vault, Doppler). Never commit keys to git. Rotate on a schedule using PATCH /api-keys/{id} followed by DELETE /api-keys/{id} for the old key.

Brute-force protection

Failed login attempts are recorded as login_failed security events with the source IP and User-Agent. Repeated invalid API key presentations from the same IP trigger a 429 Too Many Requests response — back off and verify the key before retrying.

See also