OAuth2 Authentication
Snapbooks API uses OAuth2 for secure authentication and authorization. This provides a robust framework for secure API access while allowing fine-grained control over permissions.
Overview
OAuth2 is an industry-standard protocol that allows secure API authorization in a simple and standard way. Snapbooks supports three OAuth2 grant types:
- Authorization Code Grant - For applications that can securely store client secrets (web applications)
- Authorization Code with PKCE - For public clients that cannot securely store secrets (mobile/single-page apps)
- Client Credentials Grant - For server-to-server API access
OAuth2 Endpoints
| Endpoint | Description |
|---|---|
POST /v2/oauth/register |
Self-service dynamic client registration (RFC 7591, no authentication required) |
POST /v2/oauth/clients |
Register a new OAuth client application (authenticated) |
GET /v2/oauth/clients |
List registered OAuth clients |
DELETE /v2/oauth/clients/{client_id} |
Delete an OAuth client |
GET /v2/oauth/authorize |
Authorization endpoint to obtain user consent |
POST /v2/oauth/token |
Token endpoint to obtain access and refresh tokens |
POST /v2/oauth/revoke |
Revocation endpoint to invalidate tokens |
Scopes
The API supports two scopes:
| Scope | Description |
|---|---|
read |
Read access to the data the authorizing user can see |
write |
Create and change data (write implies read) |
Enforcement is on the read/write dimension: a token without the write
scope cannot perform mutating requests (POST, PUT, PATCH, DELETE),
and a token with no scopes at all cannot access the API. If you register or
authorize without requesting a scope, read is granted by default.
Legacy granular scope names from earlier versions of this API
(read:profile, write:profile, read:accounting, write:accounting)
are still accepted as deprecated aliases and map onto read/write.
Don’t use them in new integrations.
Client Registration
There are three ways to get client credentials:
- Snapbooks web app — click your avatar and choose API access to create and manage API clients in the UI (also reachable from Settings → Apps & integrations). The client secret is shown once at creation.
- Dynamic client registration —
POST /v2/oauth/register(no authentication required, RFC 7591). Limited to user-delegated flows (authorization_code+refresh_token); theclient_credentialsgrant is not available to self-registered clients. - Authenticated registration —
POST /v2/oauth/clientswith a Bearer token from a logged-in Snapbooks session (the same token the web app uses). This is the only way to register a client for the server-to-serverclient_credentialsgrant.
Self-service registration (RFC 7591)
POST /v2/oauth/register
Content-Type: application/json
{
"client_name": "My Accounting App",
"redirect_uris": ["https://myapp.example.com/callback"],
"grant_types": ["authorization_code", "refresh_token"],
"scope": "read write"
}
Response:
{
"client_id": "<client-id>",
"client_secret": "<client-secret>",
"client_name": "My Accounting App",
"redirect_uris": ["https://myapp.example.com/callback"],
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"],
"scope": "read write",
"token_endpoint_auth_method": "client_secret_post",
"client_id_issued_at": 1712754455,
"client_secret_expires_at": 0
}
Register a new client (authenticated)
{your-session-token} is the Bearer token of a logged-in Snapbooks user —
the token your browser or mobile session uses. The registered client belongs
to that user, and tokens issued to it act on that user’s client accounts.
POST /v2/oauth/clients
Authorization: Bearer {your-session-token}
Content-Type: application/json
{
"client_name": "My Accounting App",
"redirect_uris": "https://myapp.example.com/callback",
"grant_types": "authorization_code refresh_token",
"scope": "read write"
}
Response:
{
"client_id": "<client-id>",
"client_secret": "<client-secret>",
"client_name": "My Accounting App",
"redirect_uris": "https://myapp.example.com/callback",
"grant_types": "authorization_code refresh_token",
"scope": "read write"
}
Important: Store the client_id and client_secret securely - the secret will not be retrievable later.
List registered clients
GET /v2/oauth/clients
Authorization: Bearer {your-session-token}
Response:
{
"clients": [
{
"client_id": "<client-id>",
"client_name": "My Accounting App",
"redirect_uris": "https://myapp.example.com/callback",
"grant_types": "authorization_code refresh_token",
"scope": "read write",
"created_at": "2025-04-10T14:27:35.123456"
}
]
}
Delete a client
DELETE /v2/oauth/clients/{client_id}
Authorization: Bearer {your-session-token}
Response:
{
"status": "success",
"message": "Client deleted"
}
Authorization Code Flow
This flow is suitable for web applications that can securely store client secrets.
1. Request Authorization Code
GET /v2/oauth/authorize?
response_type=code&
client_id={client_id}&
redirect_uri=https%3A%2F%2Fmyapp.example.com%2Fcallback&
scope=read%20write&
state={random_state_value}
Parameters:
response_type: Must becodeclient_id: Your application’s client IDredirect_uri: URL-encoded callback URL (must match a pre-registered value)scope: Space-separated list of requested permissionsstate: Random string to prevent CSRF attacks
The user will be prompted to log in (if not already) and authorize your application’s access.
2. Exchange Code for Tokens
After the user authorizes access, they’re redirected to your application:
https://myapp.example.com/callback?code={authorization_code}&state={random_state_value}
Verify that the state matches the one you generated, then exchange the code for tokens:
POST /v2/oauth/token
Content-Type: application/x-www-form-urlencoded
Authorization: Basic {base64(client_id:client_secret)}
grant_type=authorization_code&
code={authorization_code}&
redirect_uri=https%3A%2F%2Fmyapp.example.com%2Fcallback
Response:
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "def50200fe37bb71b1baba9b5f2d5...",
"scope": "read write"
}
Authorization Code with PKCE
For public clients (like mobile/SPA apps) that can’t securely store secrets, use PKCE (Proof Key for Code Exchange):
1. Generate a Code Verifier and Challenge
// Generate a random code verifier
const codeVerifier = generateRandomString(64);
// Create the code challenge using S256 method
const codeChallenge = base64UrlEncode(sha256(codeVerifier));
2. Request Authorization Code with PKCE
GET /v2/oauth/authorize?
response_type=code&
client_id={client_id}&
redirect_uri=https%3A%2F%2Fmyapp.example.com%2Fcallback&
scope=read%20write&
state={random_state_value}&
code_challenge={code_challenge}&
code_challenge_method=S256
3. Exchange Code for Tokens with PKCE
POST /v2/oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&
client_id={client_id}&
code={authorization_code}&
redirect_uri=https%3A%2F%2Fmyapp.example.com%2Fcallback&
code_verifier={code_verifier}
Client Credentials Flow
For server-to-server authentication without user involvement:
POST /v2/oauth/token
Content-Type: application/x-www-form-urlencoded
Authorization: Basic {base64(client_id:client_secret)}
grant_type=client_credentials&
scope=read%20write
Response:
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "read write"
}
Using Refresh Tokens
Access tokens expire after 1 hour. Use a refresh token to get a new access token:
POST /v2/oauth/token
Content-Type: application/x-www-form-urlencoded
Authorization: Basic {base64(client_id:client_secret)}
grant_type=refresh_token&
refresh_token={refresh_token}
Response:
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "def50200ca01d5cf123b6ba9c5f2a...", // New refresh token
"scope": "read write"
}
Note: Snapbooks uses token rotation for security. Each time you use a refresh token, you’ll receive a new one, and the old one becomes invalid.
Revoking Tokens
When a user logs out or you no longer need access:
POST /v2/oauth/revoke
Content-Type: application/x-www-form-urlencoded
Authorization: Basic {base64(client_id:client_secret)}
token={refresh_token}&
token_type_hint=refresh_token
Making Authenticated Requests
Use the access token for all API requests:
GET /v2/bank-accounts?client_account_id={client_account_id}
Authorization: Bearer {access_token}
Error Responses
OAuth2 endpoints return standard error responses:
| Error | Description |
|---|---|
invalid_request |
The request is missing a required parameter |
invalid_client |
Client authentication failed |
invalid_grant |
The provided authorization code or refresh token is invalid |
unauthorized_client |
The client is not authorized to use the requested grant type |
unsupported_grant_type |
The requested grant type is not supported |
invalid_scope |
The requested scope is invalid or unknown |
access_denied |
The user denied the authorization request |
Example error response:
{
"error": "invalid_grant",
"error_description": "Refresh token expired"
}
Rate Limits
The authentication endpoints are rate-limited per client IP:
| Endpoint | Limit |
|---|---|
POST /v2/oauth/token |
60 per minute |
POST /v2/oauth/revoke |
60 per minute |
GET /v2/oauth/authorize |
120 per hour |
POST /v2/oauth/login |
60 per hour |
POST /v2/oauth/register |
10 per hour |
Exceeding a limit returns 429 Too Many Requests. Cache access tokens for
their full lifetime (1 hour) instead of requesting a new token per API call.
Security Best Practices
- Store tokens securely:
- Never store tokens in localStorage in browser apps
- Use secure HTTP-only cookies or secure storage mechanisms
- For mobile apps, use the platform’s secure storage (Keychain/Keystore)
-
Implement proper PKCE for public clients (mobile/SPA apps)
-
Always validate state parameters to prevent CSRF attacks
-
Request minimal scopes - only ask for the permissions your app needs
- Implement token management:
- Handle token expiration gracefully
- Revoke tokens when a user logs out
- Implement proper error handling for authentication failures
- Norwegian regulatory compliance:
- Ensure GDPR compliance with proper user consent
- Follow Datatilsynet guidelines for storing personal data
- Implement appropriate logging for audit purposes according to Norwegian bookkeeping regulations
Discovery Endpoints
Snapbooks provides OAuth 2.0 discovery endpoints for automatic client configuration:
GET /.well-known/oauth-authorization-server— authorization server metadata (RFC 8414)GET /.well-known/oauth-protected-resource— protected resource metadata (RFC 9728)
These let OAuth clients discover endpoint URLs, supported grant types, and scopes without hard-coding them. See OAuth Discovery for full details.
Related Resources
- OAuth Clients — register and manage OAuth client applications
- OAuth Admin — admin endpoints for listing and revoking tokens
- OAuth Discovery — authorization server and protected resource metadata
- Signup — ID-porten verified user registration