Guides

Authentication

The API supports two authentication methods: API keys for direct integrations with a single company, and OAuth 2.0 for approved partner applications that act on behalf of Talero customers. Both are server-side only — never embed credentials in a browser or mobile app.

API keys

A company owner creates API keys in Talero under Indstillinger → API-nøgler (Settings → API keys). Scopes are chosen at creation time and cannot be broadened afterwards — create a new key instead. The full key is shown exactly once.

Keys have the format tlr_live_ followed by 48 hex characters and are sent in the X-API-Key header:

curl https://app.talero.dk/api/v1/invoices \
  -H "X-API-Key: tlr_live_4f3a2b1c0d9e8f7a6b5c4d3e2f1a0b9c8d7e6f5a4b3c2d1e"

Treat API keys like passwords: store them in a server-side secret manager, never commit them to source control, and rotate them by creating a replacement key and revoking the old one.

Test mode

Keys come in two flavours. A live key (tlr_live_) works on the company's real books. A test key (tlr_test_) works on a sandbox company: a separate company, provisioned automatically the first time a test key is created, owned by the same user and ready for use — a standard Danish chart of accounts, an open fiscal year, and VAT periods matching the company's reporting frequency. It mirrors the live company's settings (currency, VAT registration, fiscal year bounds) but none of its bookkeeping data.

Create one under Indstillinger → API-nøgler by turning off live mode; the create call sends "live_mode": false. Both key formats are 48 hex characters after the prefix and are sent in the same X-API-Key header:

curl https://app.talero.dk/api/v1/invoices \
  -H "X-API-Key: tlr_test_9c8d7e6f5a4b3c2d1e0f9a8b7c6d5e4f3a2b1c0d9e8f7a6b"

Everything an integration does with a test key — invoices, ledger postings, VAT-relevant data, SAF-T exports, vouchers — happens inside the sandbox company and can never touch the real books. Test and live keys are otherwise identical: the same base URL, the same endpoints, the same scopes, the same rate limits, the same envelopes and the same error contract. Build and certify against a test key, then issue a live key with the same scopes to go into production. Company owners can inspect the sandbox's data in the Talero app via Indstillinger → API-nøgler → Se sandkassedata.

Live keyTest key
Prefixtlr_live_tlr_test_
Operates onThe company's real booksAn isolated sandbox company
Chart of accountsThe company's ownSeeded standard Danish chart
Fiscal year & VAT periodsThe company's ownSeeded for the current year, same reporting frequency
Endpoints, scopes, rate limitsIdentical
Webhook envelope livemodetruefalse

Webhook subscriptions created with a test key deliver sandbox events with "livemode": false on the envelope, so one receiver can serve both environments. See Delivery format.

Sandbox data is bookkeeping data like any other — it is simply not part of the company's real accounts. Do not push production customer records into it, and do not rely on it for anything you need to keep.

Resetting the sandbox

POST /api/v1/sandbox/reset wipes the sandbox back to a freshly-provisioned state — useful for re-running an integration test suite from a clean slate. API keys and webhook subscriptions survive, and request audit records are retained; all bookkeeping and master data, including stored idempotency keys, is deleted, and the sandbox is re-seeded with the chart of accounts, fiscal year and VAT periods. Only tlr_test_ keys may call it, with any scope; live keys and OAuth tokens receive 403. No Idempotency-Key header is needed — the operation is naturally idempotent.

curl -X POST https://app.talero.dk/api/v1/sandbox/reset \
  -H "X-API-Key: tlr_test_9c8d7e6f5a4b3c2d1e0f9a8b7c6d5e4f3a2b1c0d9e8f7a6b"

Company owners can also reset from Indstillinger → API-nøgler → Nulstil sandkasse.

OAuth 2.0 grants always address the real company; there is no sandbox OAuth flow. Use a test key while developing a partner integration.

Scopes

Each API key and OAuth grant carries an explicit list of scopes. Requests to an endpoint outside the granted scopes fail with 403 forbidden. There are 19 scopes:

ScopeGrants
invoices:read / invoices:writeRead and manage invoices
customers:read / customers:writeRead and manage customers
products:read / products:writeRead and manage products
expenses:read / expenses:writeRead and manage expenses
accounts:read / accounts:writeRead and manage the chart of accounts
transactions:read / transactions:writeRead and post ledger transactions
suppliers:read / suppliers:writeRead and manage suppliers
vouchers:read / vouchers:writeRead and download vouchers; upload and delete them — see Vouchers
reports:readRead financial reports
exports:readGenerate SAF-T exports
webhooks:manageManage webhook subscriptions, deliveries, replay and secret rotation

Request the minimum set of scopes your integration needs.

OAuth 2.0 for partner applications

Partner applications use the OAuth 2.0 Authorization Code flow with PKCE (RFC 7636, S256 — mandatory). Only confidential clients are supported: your application must be able to keep a client_secret on a server.

1. Register a client

Client registration is a manual process. Email [email protected] with your application name, a technical contact, the exact HTTPS redirect URIs and the scopes you need. Talero provisions a confidential client and shares the client_id and client_secret (the secret is shown once).

2. Generate a PKCE verifier and challenge

For every authorization request, generate a fresh high-entropy code_verifier and derive the code_challenge as the base64url-encoded (unpadded) SHA-256 hash of the verifier:

Node.js
const crypto = require('crypto');

const codeVerifier = crypto.randomBytes(32).toString('base64url');
const codeChallenge = crypto
  .createHash('sha256')
  .update(codeVerifier)
  .digest('base64url');
Shell (OpenSSL)
code_verifier=$(openssl rand -base64 48 | tr '+/' '-_' | tr -d '=')
code_challenge=$(printf '%s' "$code_verifier" \
  | openssl dgst -sha256 -binary \
  | openssl base64 -A | tr '+/' '-_' | tr -d '=')

Store the code_verifier server-side (bound to the pending authorization via state) — you need it in step 4.

3. Send the user to the authorization page

Redirect the user's browser to the authorization URL. The user logs in to Talero, selects which of their companies the application may access, and approves the requested scopes.

https://app.talero.dk/oauth/authorize
  ?client_id=YOUR_CLIENT_ID
  &redirect_uri=https%3A%2F%2Fexample.com%2Fcallback
  &state=af0ifjsldkj
  &scope=invoices%3Aread%20customers%3Aread
  &code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
  &code_challenge_method=S256
ParameterDescription
client_idYour client identifier.
redirect_uriOne of the exact HTTPS redirect URIs registered for the client.
stateOpaque CSRF token. Verify it on the callback before exchanging the code.
scopeSpace-separated scopes, a subset of what the client was registered with.
code_challengeBase64url (unpadded) SHA-256 of the code_verifier.
code_challenge_methodMust be S256. The plain method is not supported.

On approval, Talero redirects back to your redirect_uri with code and state query parameters. Authorization codes are single-use and short-lived.

4. Exchange the code for tokens

From your server, call the token endpoint with a JSON body containing the code, your client credentials and the PKCE verifier:

curl -X POST https://app.talero.dk/api/oauth/token \
  -H "Content-Type: application/json" \
  -d '{
    "grant_type": "authorization_code",
    "code": "tlr_code_...",
    "redirect_uri": "https://example.com/callback",
    "client_id": "YOUR_CLIENT_ID",
    "client_secret": "YOUR_CLIENT_SECRET",
    "code_verifier": "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
  }'
Response — 200 OK
{
  "access_token": "tlr_at_...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "tlr_rt_...",
  "scope": "invoices:read customers:read"
}

5. Call the API

Send the access token as a Bearer token. OAuth-authenticated requests use the same endpoints, envelopes and rate limits as API keys.

curl https://app.talero.dk/api/v1/customers \
  -H "Authorization: Bearer tlr_at_..."

6. Refresh tokens

Access tokens expire after one hour. Use the refresh token to obtain a new pair:

curl -X POST https://app.talero.dk/api/oauth/token \
  -H "Content-Type: application/json" \
  -d '{
    "grant_type": "refresh_token",
    "refresh_token": "tlr_rt_...",
    "client_id": "YOUR_CLIENT_ID",
    "client_secret": "YOUR_CLIENT_SECRET"
  }'

Refresh tokens are single-use: each refresh returns a new refresh token and invalidates the old one. If a previously used refresh token is replayed, Talero treats it as a possible theft and revokes the entire token family — the user must re-authorize. Persist the newest refresh token atomically before discarding the old one.

7. Revoke tokens

Revoke a token you no longer need (RFC 7009), for example when a user disconnects your application:

curl -X POST https://app.talero.dk/api/oauth/revoke \
  -H "Content-Type: application/json" \
  -d '{
    "token": "tlr_rt_...",
    "token_type_hint": "refresh_token",
    "client_id": "YOUR_CLIENT_ID",
    "client_secret": "YOUR_CLIENT_SECRET"
  }'

Token lifetimes

CredentialFormatLifetime
API key (live)tlr_live_ + 48 hex charsUntil revoked (optional expiry at creation)
API key (test)tlr_test_ + 48 hex charsUntil revoked (optional expiry at creation)
Access tokentlr_at_...1 hour
Refresh tokentlr_rt_...30 days, single-use with rotation

The OAuth token endpoint is rate limited separately from the API: 10 requests per 60 seconds per client and IP address. See Rate limits.