My App

KSeF Sessions

Opening KSeF sessions, token/XAdES authentication, session lifecycle, and auto-recovery.

Overview

Before submitting invoices, you need an active KSeF session. The session authenticates your company with the Ministry of Finance and provides encryption keys for invoice submission.

┌───────────┐     ┌─────────────┐     ┌──────────────┐     ┌─────────┐
│ Credential│     │  Gateway    │     │   KSeF API   │     │ Session │
│ (token or │────▶│  1. Decrypt │────▶│  2. Challenge│────▶│ active  │
│ cert)     │     │  3. Encrypt │     │  4. Auth     │     │ (~1h)   │
└───────────┘     │  5. Store   │     └──────────────┘     └─────────┘
                  └─────────────┘

Key facts:

  • Sessions last approximately 1 hour (set by KSeF)
  • The Gateway automatically reuses active sessions — creating a session when one exists returns the existing one
  • When a session expires during invoice submission, the Gateway automatically opens a new one
  • Sessions are per company — each company has its own KSeF session

Create Session (Token-Based)

The simplest way. Requires a stored KSeF token credential (Tokens & Certificates).

POST /api/sessions/
Authorization: Bearer {jwt_token}
Content-Type: application/json

Request:

{
  "company_id": "22919080-f3fe-4aff-8d71-f6493afc956a",
  "session_type": "interactive",
  "is_test_mode": false
}
FieldTypeRequiredDefaultDescription
company_idUUID✅—Company to authenticate
session_typestring❌interactiveinteractive (online) or batch
is_test_modeboolean❌falseUse KSeF test environment

Response (201):

{
  "session_id": "faeebfa8-78e0-41af-a1f8-40c76575bcc6",
  "company_id": "22919080-f3fe-4aff-8d71-f6493afc956a",
  "session_token": "eyJhbGciOiJIUzI1NiIs...(KSeF JWT)",
  "session_type": "interactive",
  "status": "active",
  "ksef_session_reference": "20260314-SO-2AD5D7C000-D5D99D891F-B6",
  "context_identifier": null,
  "created_at": "2026-03-14T12:28:35.993816Z",
  "expires_at": "2026-03-14T13:28:36.152333Z",
  "terminated_at": null,
  "is_test_mode": false,
  "is_active": true
}

Session reuse: If a company already has an active session, this endpoint returns the existing session instead of creating a new one. This is by design — KSeF limits concurrent sessions per NIP.

What happens internally:

  1. Gateway retrieves active KSeF credential from DB (decrypts)
  2. Calls KSeF POST /auth/challenge → gets challenge + timestamp
  3. Fetches MF public key (KsefTokenEncryption)
  4. Encrypts token with RSA using MF public key
  5. Calls KSeF POST /auth/ksef-token → receives session reference + encryption keys
  6. Stores session in DB and Redis (for fast access)

Authenticate with KSeF (Alternative)

Direct authentication endpoint — opens a session and stores tokens internally.

POST /api/companies/{company_id}/ksef/authenticate
Authorization: Bearer {jwt_token}

Response (200):

{
  "success": true,
  "message": "Successfully authenticated with KSeF",
  "access_token_preview": "eyJhbGciOiJIUzI1NiIs...JrIiDEFDjK8O1h0HYdls",
  "refresh_token_preview": "eyJhbGciOiJIUzI1NiIs...uCSi-t5cdz5K1rEGuJ98"
}

This endpoint is functionally equivalent to POST /api/sessions/ but returns less detail. Use POST /api/sessions/ for full session information.


XAdES Authentication (Certificate-Based)

Two-step flow for organizations using X.509 certificates instead of tokens.

Step 1: Get Challenge

POST /api/companies/{company_id}/ksef/auth/challenge
Authorization: Bearer {jwt_token}
Content-Type: application/json

Request (optional):

{
  "identifier_type": "nip",
  "identifier": "8133793363"
}
FieldTypeRequiredDefaultDescription
identifier_typestring❌nipnip or pesel
identifierstring❌company NIPNIP or PESEL value

Response (200):

{
  "challenge": "20260314-CR-2AD6E39000-D05B5F7A...",
  "timestamp": "2026-03-14T12:32:13.1483963+00:00",
  "expires_at": "2026-03-14T12:42:13.172422+00:00",
  "xml_to_sign": "<?xml version='1.0'...AuthTokenRequest...335 chars",
  "xml_base64": "PD94bWwgdm..."
}

The challenge expires in 10 minutes. You must sign and submit before it expires.

Step 2: Submit Signed XAdES

Sign xml_to_sign with your qualified certificate using XAdES-BES, then submit:

POST /api/companies/{company_id}/ksef/auth/xades
Authorization: Bearer {jwt_token}
Content-Type: application/json

Request:

{
  "signed_xml": "<?xml version='1.0'...<ds:Signature>...</Signature>...</xml>",
  "signed_xml_base64": null
}

Provide either signed_xml (string) or signed_xml_base64 (Base64-encoded).

Response (200):

{
  "session_id": "abc...",
  "ksef_reference_number": "20260314-AU-...",
  "expires_at": "2026-03-14T13:32:13Z",
  "auth_type": "xades",
  "message": "XAdES authentication successful. Use session_id in X-KSeF-Session header."
}

Session Management

Get Active Session

GET /api/sessions/company/{company_id}/active
Authorization: Bearer {jwt_token}

Response (200): Full SessionResponse of the active session.

Response (404): No active session for this company.

Check Session Status

GET /api/sessions/{session_id}/status
Authorization: Bearer {jwt_token}

Response (200):

{
  "session_id": "faeebfa8-78e0-41af-a1f8-40c76575bcc6",
  "status": "active",
  "is_active": true,
  "expires_at": "2026-03-14T13:28:36.152333Z",
  "time_remaining_seconds": 3398
}

time_remaining_seconds tells you exactly how much time is left before automatic expiry. Plan invoice submissions accordingly.

Get Session by ID

GET /api/sessions/{session_id}
Authorization: Bearer {jwt_token}

Returns full SessionResponse including session_token, ksef_session_reference, timestamps.

List Sessions

GET /api/sessions/?company_id={id}&status={status}&limit={n}&offset={n}
Authorization: Bearer {jwt_token}
ParameterTypeRequiredDescription
company_idUUID❌Filter by company
statusstring❌active, expired, terminated
limitinteger❌Results per page
offsetinteger❌Pagination offset

Response (200):

{
  "sessions": [
    {
      "session_id": "faeebfa8-...",
      "status": "active",
      "ksef_session_reference": "20260314-SO-2AD5D7C000-...",
      "is_active": true,
      "created_at": "2026-03-14T12:28:35Z",
      "expires_at": "2026-03-14T13:28:36Z"
    },
    {
      "session_id": "0d861405-...",
      "status": "expired",
      "ksef_session_reference": "20260314-SO-26E90F4000-...",
      "is_active": false,
      "created_at": "2026-03-14T11:19:59Z",
      "expires_at": "2026-03-14T12:20:00Z"
    }
  ],
  "total": 5
}

KSeF Token Management (Within Session)

Get Token Info

GET /api/companies/{company_id}/ksef/token-info
Authorization: Bearer {jwt_token}

Response (200):

{
  "has_access_token": true,
  "has_refresh_token": true,
  "access_token_expires_at": "2026-03-14T12:39:56.358356+00:00",
  "access_token_expires_soon": false
}

Get KSeF Access Token

GET /api/companies/{company_id}/ksef/access-token
Authorization: Bearer {jwt_token}

Returns the raw KSeF access token (for direct KSeF API calls, advanced use).

Refresh KSeF Access Token

POST /api/companies/{company_id}/ksef/refresh-token
Authorization: Bearer {jwt_token}

Response (200):

{
  "accessToken": {
    "token": "eyJhbGciOiJIUzI1NiIs...",
    "validUntil": "2026-03-14T12:47:13.541304Z"
  }
}

The Gateway handles token refresh automatically during invoice submission. Use this only if you need to extend a session manually.

Invalidate All Tokens

DELETE /api/companies/{company_id}/ksef/invalidate-tokens
Authorization: Bearer {jwt_token}

Revokes all cached KSeF tokens for the company (access + refresh). The next invoice submission will trigger a fresh authentication.


Closing Sessions

Close Current Session

POST /api/companies/{company_id}/ksef/terminate-session
Authorization: Bearer {jwt_token}

Response (200):

{
  "success": true,
  "message": "Session terminated successfully",
  "referenceNumber": "session-terminated"
}

Close Online (Interactive) Session by Reference

POST /api/sessions/online/{reference_number}/close?company_id={company_id}
Authorization: Bearer {jwt_token}

Close Batch Session by Reference

POST /api/sessions/batch/{reference_number}/close?company_id={company_id}
Authorization: Bearer {jwt_token}

Terminate All Sessions

POST /api/companies/{company_id}/ksef/terminate-all-sessions
Authorization: Bearer {jwt_token}

Response (200):

{
  "success": true,
  "message": "All sessions terminated successfully",
  "referenceNumber": "batch-terminate",
  "terminatedSessionsCount": 3
}

Terminate by Session ID

POST /api/sessions/terminate
Authorization: Bearer {jwt_token}
Content-Type: application/json
{
  "session_id": "faeebfa8-78e0-41af-a1f8-40c76575bcc6"
}

Invoices Within Session

List Invoices in Session

GET /api/sessions/{reference_number}/invoices?company_id={id}&page_size=50&page_offset=0
Authorization: Bearer {jwt_token}

Response (200):

{
  "continuationToken": null,
  "invoices": [
    {
      "referenceNumber": "20260110-SE-1234567890-ABCDEF1234-12",
      "ordinalNumber": 1,
      "invoicingDate": "2026-01-10T12:00:00Z",
      "invoiceHash": "abc123...",
      "status": { "code": 200, "description": "Sukces" }
    }
  ]
}

List Failed Invoices in Session

GET /api/sessions/{reference_number}/invoices/failed?company_id={id}
Authorization: Bearer {jwt_token}

Response (200):

{
  "continuationToken": null,
  "invoices": [
    {
      "referenceNumber": "20260110-SE-...",
      "ordinalNumber": 1,
      "invoiceHash": "abc123...",
      "status": { "code": 430, "description": "Invoice file verification error" }
    }
  ]
}

Get Invoice UPO in Session

GET /api/sessions/{session_reference}/invoices/ksef/{ksef_reference_number}/upo?company_id={id}
Authorization: Bearer {jwt_token}

Get Session Collective UPO

GET /api/sessions/{session_reference}/upo/{upo_reference_number}?company_id={id}
Authorization: Bearer {jwt_token}

Auth Sessions (Keycloak/KSeF)

Manage authentication sessions at the Keycloak/KSeF level.

GET    /api/auth/sessions?company_id={id}           List active auth sessions
DELETE /api/auth/sessions/current?company_id={id}    Revoke current auth session
DELETE /api/auth/sessions/{ref}?company_id={id}      Revoke by reference number
GET    /api/auth/{ref}?auth_token={token}            Check authentication status

Session Lifecycle

                    ┌─────────┐
POST /sessions/ ───▶│ ACTIVE  │──── ~1 hour TTL
                    └────┬────┘
                         │
            ┌────────────┼────────────┐
            ▼            ▼            ▼
     ┌──────────┐  ┌──────────┐  ┌──────────┐
     │ EXPIRED  │  │TERMINATED│  │  (auto)  │
     │ (timeout)│  │ (manual) │  │ new auth │
     └──────────┘  └──────────┘  └──────────┘
StatusMeaningCan submit invoices?
activeSession is open and valid✅ Yes
expiredTTL exceeded (~1 hour)❌ No — Gateway auto-creates new session
terminatedManually closed❌ No — create new session

Auto-recovery: When invoice submission encounters an expired session, the Gateway automatically:

  1. Detects 401 from KSeF
  2. Opens a new session
  3. Retries the invoice submission
  4. This is transparent to the API client

Error Responses

HTTPErrorCause
200/201—Success
400No active credential foundCompany has no KSeF token/certificate
401Not authenticatedMissing JWT
403Access denied to this companyWrong tenant
404Company not foundInvalid company_id
404No active sessionNo active session for company
409Session already existsRarely — concurrent session creation
500KSeF authentication failedKSeF API error (check KSeF status)

Endpoint Reference

Session Management

MethodEndpointDescription
POST/api/sessions/Create new session (or return existing)
GET/api/sessions/List sessions (filter: company_id, status)
GET/api/sessions/{session_id}Get session by ID
GET/api/sessions/{session_id}/statusCheck status + time_remaining
GET/api/sessions/company/{company_id}/activeGet active session
POST/api/sessions/online/{ref}/closeClose interactive session
POST/api/sessions/batch/{ref}/closeClose batch session
POST/api/sessions/terminateTerminate by session_id

KSeF Authentication

MethodEndpointDescription
POST/api/companies/{id}/ksef/authenticateAuthenticate (token-based)
POST/api/companies/{id}/ksef/auth/challengeGet XAdES challenge
POST/api/companies/{id}/ksef/auth/xadesSubmit signed XAdES
GET/api/companies/{id}/ksef/access-tokenGet raw KSeF access token
GET/api/companies/{id}/ksef/token-infoToken status info
POST/api/companies/{id}/ksef/refresh-tokenRefresh access token
DELETE/api/companies/{id}/ksef/invalidate-tokensRevoke all cached tokens
POST/api/companies/{id}/ksef/terminate-sessionClose current session
POST/api/companies/{id}/ksef/terminate-all-sessionsClose all sessions

Invoices in Session

MethodEndpointDescription
GET/api/sessions/{ref}/invoicesList invoices in session
GET/api/sessions/{ref}/invoices/failedList failed invoices
GET/api/sessions/{ref}/invoices/ksef/{ksef_ref}/upoInvoice UPO in session
GET/api/sessions/{ref}/upo/{upo_ref}Session collective UPO

Auth Sessions

MethodEndpointDescription
GET/api/auth/sessionsList active auth sessions
DELETE/api/auth/sessions/currentRevoke current
DELETE/api/auth/sessions/{ref}Revoke by reference
GET/api/auth/{ref}Check auth status

Complete Example

#!/bin/bash
TOKEN="your_jwt_token"
BASE="https://demo.ksefapi.com"
COMPANY_ID="22919080-f3fe-4aff-8d71-f6493afc956a"

# 1. Create session (or reuse active)
SESSION=$(curl -s -X POST "$BASE/api/sessions/" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"company_id\": \"$COMPANY_ID\"}")
echo "$SESSION" | jq '{session_id, status, ksef_session_reference, expires_at}'

# 2. Check status
SESSION_ID=$(echo "$SESSION" | jq -r '.session_id')
curl -s "$BASE/api/sessions/$SESSION_ID/status" \
  -H "Authorization: Bearer $TOKEN" | jq '{status, time_remaining_seconds}'

# 3. Token info
curl -s "$BASE/api/companies/$COMPANY_ID/ksef/token-info" \
  -H "Authorization: Bearer $TOKEN" | jq .

# 4. Submit invoices → see Invoice Submission docs
# (Session is now active, invoice submission will use it automatically)

# 5. When done, close session (optional — expires automatically)
curl -s -X POST "$BASE/api/companies/$COMPANY_ID/ksef/terminate-session" \
  -H "Authorization: Bearer $TOKEN" | jq .

Important Notes

Session reuse: POST /api/sessions/ is idempotent for active sessions. Calling it multiple times returns the same session. No need to check for existing sessions before creating.

Auto-session management: For most use cases, you don't need to manage sessions manually. The Gateway creates sessions automatically when you submit an invoice. Sessions are needed explicitly only if you want to pre-authenticate or inspect session state.

Session TTL: KSeF sessions expire after approximately 1 hour. The exact time is in expires_at. After expiry, the Gateway auto-creates a new session on the next invoice submission.

Interactive vs batch sessions: Interactive sessions are for real-time invoice submission (online). Batch sessions are for large file uploads (offline). Most API integrations use interactive.

KSeF session token: The session_token in the response is a KSeF-issued JWT (not your API JWT). It's used internally by the Gateway for KSeF API calls. You don't need to use it directly unless making raw KSeF API calls.

Next steps:


On this page