Authentication & Authorization
JWT tokens, OAuth2 flows, M2M credentials, token refresh, and multi-tenancy context.
Every API call (except health checks and incoming webhooks) requires a JWT Bearer token. The token carries your identity, tenant context, and role — the API uses it to enforce access control and data isolation.
Your App → OAuth2 Server → JWT → KSeF API Gateway (validates)Base URLs
| Environment | Auth Server | API |
|---|---|---|
| Demo | https://demo-auth.ksefapi.com | https://demo.ksefapi.com |
| Production | https://auth.ksefapi.com | https://gateway.ksefapi.com |
M2M — Client Credentials (recommended)
Machine-to-machine authentication. No user interaction required. Best for ERP systems, automated pipelines, and API integrations.
curl -X POST https://demo-auth.ksefapi.com/realms/ksef-realm/protocol/openid-connect/token \
-d 'client_id=YOUR_CLIENT_ID' \
-d 'client_secret=YOUR_CLIENT_SECRET' \
-d 'grant_type=client_credentials'| Parameter | Type | Required | Description |
|---|---|---|---|
client_id | string | ✅ | M2M client ID |
client_secret | string | ✅ | M2M client secret |
grant_type | string | ✅ | Must be client_credentials |
Response (200):
{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 300
}Client credentials grant does not return a
refresh_token. When the token expires (300s = 5 min), request a new one with the same call.
User Login — Password Grant
For user-facing applications where you collect username and password.
POST /api/auth/login
Content-Type: application/jsonRequest:
{
"email": "[email protected]",
"password": "SecurePassword123!"
}Response (200):
{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 300,
"refresh_token": "eyJhbGciOiJIUzI1NiIs...",
"refresh_expires_in": 1800
}| Field | Description |
|---|---|
access_token | JWT for API calls (5 min TTL) |
refresh_token | Use to get new access_token without re-login (30 min TTL) |
expires_in | Access token lifetime in seconds |
refresh_expires_in | Refresh token lifetime in seconds |
User Login — Browser Redirect (OAuth2 Authorization Code)
For web applications using the OAuth2 login page.
# Get auth URLs
curl https://demo.ksefapi.com/api/auth/urls{
"login_url": "https://demo-auth.ksefapi.com/realms/ksef-realm/protocol/openid-connect/auth?...",
"register_url": "https://demo-auth.ksefapi.com/realms/ksef-realm/protocol/openid-connect/registrations?...",
"login_redirect": "/api/auth/login-url",
"register_redirect": "/api/auth/register-url"
}Flow:
- Redirect user to
login_url - User authenticates on the OAuth2 login page
- User is redirected back to your
redirect_uriwith an authorizationcode - Exchange the code for tokens via the callback endpoint
Token Refresh
curl -X POST https://demo.ksefapi.com/api/auth/refresh \
-H "Content-Type: application/json" \
-d '{"refresh_token": "eyJhbGciOiJIUzI1NiIs..."}'Response (200):
{
"access_token": "eyJhbGciOiJSUzI1NiIs...(new)",
"token_type": "Bearer",
"expires_in": 300,
"refresh_token": "eyJhbGciOiJIUzI1NiIs...(new)",
"refresh_expires_in": 1800
}Both tokens are rotated on refresh. Always store and use the latest refresh token.
Current User Info
curl -H "Authorization: Bearer $TOKEN" \
https://demo.ksefapi.com/api/auth/meResponse (200):
{
"sub": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"email": "[email protected]",
"name": "Jan Kowalski",
"realm_access": {
"roles": ["user"]
},
"company_associations": [
{
"company_id": "22919080-f3fe-4aff-8d71-f6493afc956a",
"company_name": "BIZNETO SP. Z O.O.",
"nip": "8133793363",
"role": "admin"
}
]
}Using the Token
Include the token in every API request:
curl -H "Authorization: Bearer $TOKEN" \
https://demo.ksefapi.com/api/companiesCommon errors:
| Status | Error | Cause |
|---|---|---|
| 401 | Token has expired | Token older than 5 min — refresh or re-authenticate |
| 401 | Invalid token | Malformed or tampered JWT |
| 403 | Insufficient permissions | Your role doesn't allow this operation |
| 403 | Company access denied | Token not associated with the requested company |
Token Lifecycle Best Practices
- Cache tokens — don't request a new token for every API call
- Refresh proactively — refresh when ~80% of TTL has elapsed (at ~4 min for a 5-min token)
- Handle 401 gracefully — catch expired token errors, refresh, and retry the request
- For M2M — simply request a new token when needed (no refresh token available)
- Store securely — never log or expose tokens in URLs or client-side storage
Authentication Endpoints Summary
| Method | Endpoint | Description |
|---|---|---|
POST | /realms/ksef-realm/protocol/openid-connect/token | Get token (M2M or password) |
POST | /api/auth/login | User login (password grant) |
POST | /api/auth/refresh | Refresh token |
GET | /api/auth/me | Current user info |
GET | /api/auth/urls | OAuth2 redirect URLs |
GET | /api/auth/login-url | Redirect to OAuth2 login |
GET | /api/auth/register-url | Redirect to registration |
GET | /api/auth/callback | OAuth2 callback handler |
POST | /api/auth/logout | Invalidate session |