SAPI-SK: authentication (OAuth2)

SAPI-SK uses the OAuth 2.0 client_credentials grant. In exchange for a client ID and secret you get a short-lived access token (JWT, 15 minutes) and a long-lived refresh token (30 days) to renew it without resending the secret.

Getting tokens

You create the client ID and secret in the partner developer portal (developers.efaktura.sk → Dashboard → SAPI-SK, path /dashboard/sapi). The secret is shown only once. Exchange them for a token pair:

SAPI-SK clients are not issued inside the eFaktúra app: the “API keys” in the app settings are REST API keys (X-API-Key), not SAPI-SK credentials. Access to the developer portal is invite-only: write to podpora@efaktura.sk and we will send you an invitation. A test (sandbox) client is available immediately and free of charge; the live client is activated once partner access is approved.
POST https://api.efaktura.sk/sapi/auth/token
Content-Type: application/json

{
  "client_id": "sapi_live_xxxxxxxxxxxx",
  "client_secret": "••••••••••••••••••••",
  "grant_type": "client_credentials",
  "scope": "document:send document:receive"
}

Response (200):

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 900,
  "scope": "document:send document:receive",
  "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refresh_expires_in": 2592000
}
The refresh_token and refresh_expires_in fields are our own extension. The original SAPI schema forgot to declare them in the response, even though the /auth/renew endpoint requires a refresh token, so we return them.

Using the access token

Send the access token in the Authorization: Bearer <access_token> header on all document calls, together with the X-Peppol-Participant-Id header.

Checking token status

Before renewing, you can check how much time is left on the token. If should_refresh is true (less than 3 minutes to expiry), proactively refresh the token.

GET https://api.efaktura.sk/sapi/auth/token/status
Authorization: Bearer <access_token>

// 200
{
  "valid": true,
  "token_type": "access",
  "client_id": "sapi_live_xxxxxxxxxxxx",
  "issued_at": "2026-07-04T10:00:00.000Z",
  "expires_at": "2026-07-04T10:15:00.000Z",
  "expires_in_seconds": 542,
  "should_refresh": false,
  "refresh_recommended_at": "2026-07-04T10:12:00.000Z"
}

Renewing tokens (rotation)

You exchange a refresh token for a new pair. This is a rotation: the original refresh token is invalidated and you receive a new one. Always store the new pair from the response.

POST https://api.efaktura.sk/sapi/auth/renew
Content-Type: application/json

{ "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." }

// 200: same shape as /auth/token (a new access and a new refresh token)
Reuse detection. If an already-rotated (invalidated) refresh token is used, we treat it as a compromise and invalidate the entire family of tokens derived from the original. In that case you must re-authenticate via /auth/token.

Invalidation (revoke)

On logout, or if you suspect a refresh token leaked, revoke it. The operation is idempotent: it always returns success (even for an unknown token, per RFC 7009).

POST https://api.efaktura.sk/sapi/auth/revoke
Content-Type: application/json

{ "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "token_type_hint": "refresh_token" }

// 200
{ "success": true, "message": "Token revoked successfully", "timestamp": "2026-07-04T10:20:00.000Z" }

Brute-force protection (lockout)

After 5 failed token attempts for a given client_id, the client is temporarily locked for 15 minutes: further calls return 423 (SAPI-AUTH-005). A successful authentication resets the counter.

IP allowlist

You can optionally set an allowed IP address list for a client. If it's set and a token request arrives from a different IP, 403 is returned (SAPI-AUTH-004).