Authentication
The Storno.ro API uses JWT (JSON Web Tokens) for authentication. You can obtain tokens via email/password login, Google OAuth, or passkey (WebAuthn) authentication.
Obtaining a Token
Email & Password
curl -X POST https://api.storno.ro/api/auth \
-H "Content-Type: application/json" \
-d '{
"email": "[email protected]",
"password": "your_password"
}'
const response = await fetch('https://api.storno.ro/api/auth', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email: '[email protected]',
password: 'your_password'
})
});
const { token, refresh_token } = await response.json();
Response:
{
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9...",
"refresh_token": "def50200a..."
}
Google OAuth
Exchange a Google ID token for API credentials:
curl -X POST https://api.storno.ro/api/auth/google \
-H "Content-Type: application/json" \
-d '{
"idToken": "google_id_token_here"
}'
Response:
{
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9...",
"refresh_token": "def50200a...",
"isNewUser": false
}
If the user doesn't exist, an account is automatically created and isNewUser will be true.
Passkey (WebAuthn)
Passkey authentication is a two-step process:
Step 1: Request login options
curl -X POST https://api.storno.ro/api/auth/passkey/login/options \
-H "Content-Type: application/json" \
-d '{
"email": "[email protected]"
}'
Returns a WebAuthn challenge that must be signed by the user's authenticator.
Step 2: Submit signed response
curl -X POST https://api.storno.ro/api/auth/passkey/login \
-H "Content-Type: application/json" \
-d '{
"response": { ... }
}'
Response:
{
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9...",
"refresh_token": "def50200a..."
}
Multi-Factor Authentication (MFA)
When a user has two-factor authentication enabled, login via email/password or Google OAuth returns an MFA challenge instead of tokens:
{
"mfa_required": true,
"mfa_token": "a1b2c3d4e5f6...",
"mfa_methods": ["totp", "backup_code"]
}
You must then complete the challenge by submitting a TOTP code or backup code:
curl -X POST https://api.storno.ro/api/auth/mfa/verify \
-H "Content-Type: application/json" \
-d '{
"mfaToken": "a1b2c3d4e5f6...",
"code": "123456",
"type": "totp"
}'
const response = await fetch('https://api.storno.ro/api/auth/mfa/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
mfaToken: 'a1b2c3d4e5f6...',
code: '123456',
type: 'totp',
}),
});
const { token, refresh_token } = await response.json();
Response:
{
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9...",
"refresh_token": "def50200a..."
}
Passkey logins skip MFA entirely. Passkeys inherently satisfy multi-factor authentication (possession of the device + biometric or PIN), so no additional challenge is required.
See the MFA API reference for details on challenge verification, and MFA Status for managing MFA settings.
Using the Token
Include the JWT token in the Authorization header of every request:
curl https://api.storno.ro/api/v1/me \
-H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9..."
API Keys
For programmatic or long-lived access, you can use API keys instead of JWT tokens. API keys use scoped permissions and are ideal for CI/CD pipelines, scripts, and integrations.
Create an API key via the Create API token endpoint or from the web application under Settings > API Keys.
API keys must be sent without the Bearer prefix. The Bearer prefix is reserved for JWT tokens.
curl https://api.storno.ro/api/v1/invoices \
-H "Authorization: af_a1b2c3d4e5f6..." \
-H "X-Company: 550e8400-e29b-41d4-a716-446655440000"
API keys use the af_ prefix for easy identification. The key's permissions are intersected with the user's role — both must grant access for a request to succeed. See API Keys for details on creating and managing keys.
Refreshing Tokens
JWT tokens expire after a configured period. Use the refresh token to obtain a new JWT without re-authenticating:
curl -X POST https://api.storno.ro/api/auth/token/refresh \
-H "Content-Type: application/json" \
-d '{
"refresh_token": "def50200a..."
}'
Response:
{
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9...",
"refresh_token": "def50200b..."
}
Both the access token and refresh token are rotated on each refresh. Store the new refresh token for subsequent refreshes.
Company Context
Most API endpoints operate within a company context. You must include the X-Company header with the company UUID:
curl https://api.storno.ro/api/v1/invoices \
-H "Authorization: Bearer {token}" \
-H "X-Company: 550e8400-e29b-41d4-a716-446655440000"
To get the list of companies you have access to:
curl https://api.storno.ro/api/v1/companies \
-H "Authorization: Bearer {token}"
Registering a Passkey
Authenticated users can register passkeys for passwordless login:
Step 1: Get registration options
curl -X POST https://api.storno.ro/api/v1/passkey/register/options \
-H "Authorization: Bearer {token}"
Step 2: Submit registration response
curl -X POST https://api.storno.ro/api/v1/passkey/register \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"name": "My MacBook",
"response": { ... }
}'
Managing Passkeys
# List passkeys
curl https://api.storno.ro/api/v1/me/passkeys \
-H "Authorization: Bearer {token}"
# Delete a passkey
curl -X DELETE https://api.storno.ro/api/v1/me/passkeys/{id} \
-H "Authorization: Bearer {token}"
Token Lifetime
| Token | Lifetime |
|---|---|
| Access token (JWT) | Configured per deployment (typically 1 hour) |
| Refresh token | Configured per deployment (typically 30 days) |
Error Responses
| Status | Description |
|---|---|
| 401 | Invalid credentials or expired token |
| 403 | Account is inactive or email not confirmed |
| 429 | Too many login attempts (rate limited) |