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"
  }'

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"
  }'

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

TokenLifetime
Access token (JWT)Configured per deployment (typically 1 hour)
Refresh tokenConfigured per deployment (typically 30 days)

Error Responses

StatusDescription
401Invalid credentials or expired token
403Account is inactive or email not confirmed
429Too many login attempts (rate limited)