How it works
- You register your app with sajn and get a client ID and a client secret.
- Your app sends the person to sajn’s authorize URL.
- The person signs in, selects an organization and a workspace, and approves the scopes your app asks for.
- sajn redirects back to your app with an authorization code.
- Your server exchanges the code for an access token and a refresh token.
- Your server calls the API with the access token, and refreshes it when it expires.
Endpoints
The
scopes_supported list in the server metadata covers only the scopes available to AI assistants that register themselves; the scope table on this page is the full list.
Register your app
App registration is open to sajn platform partners. If your organization isn’t a partner, the dashboard doesn’t let you create an app; contact [email protected] about the partner program. To register an app, a member who can manage the organization’s OAuth apps does the following:- In the sajn dashboard, go to Inställningar > Utvecklare > OAuth-appar.
- Create an app, and enter its name and logo. People see both on the consent screen.
- Add every redirect URI your app uses.
- Select the scopes your app can request.
- Copy the client ID and the client secret. The secret is shown only once; store it in a secret manager.
localhost, 127.0.0.1, or [::1]), where HTTP is allowed. A private-use scheme, such as com.example.app://callback, is also allowed. A redirect URI can’t have a fragment. At authorization time, sajn matches the URI exactly, except that it ignores the port of a loopback URI.
AI assistants that connect to the workspace MCP server register themselves instead, through dynamic client registration or a client ID metadata document. This page covers apps that you register.
Send the person to sajn
To start the flow, redirect the person’s browser to the authorize URL with the following query parameters:
The following URL is an example:
CLIENT_ID: your app’s client ID.STATE: the random value you stored in the person’s session.CODE_CHALLENGE: the code challenge you derived from your code verifier.
Use PKCE
PKCE (RFC 7636) binds the authorization code to the app that started the flow, so a code that leaks can’t be exchanged by anyone else. An app registered in the dashboard is confidential, so PKCE is optional for it, but we recommend it. Before you redirect, create a random code verifier and derive the challenge from it with SHA-256:Handle the callback
When the person approves, sajn redirects to yourredirect_uri with the code and your state:
state matches the value in the person’s session before you continue. The authorization code expires after 10 minutes and works only once.
Exchange the code for tokens
To get tokens, send aPOST request to the token endpoint from your server. The body can be form-encoded (application/x-www-form-urlencoded) or JSON, and has the following fields:
AUTHORIZATION_CODE, CLIENT_ID, CLIENT_SECRET, and CODE_VERIFIER with your values. Without PKCE, leave out code_verifier.
The response is similar to the following:
access_token: the token for API requests. It expires afterexpires_inseconds, which is 15 minutes.refresh_token: the token that gets you a new access token. Store it encrypted, together with the organization and workspace it belongs to.scope: the scopes the person granted, separated by spaces.
Call the API
Send the access token in theAuthorization header, as you would an API key:
Sajn-Version header uses the app’s version, never the default of the organization that connected the app. We recommend sending the header on every request. For more information, see OAuth app version.
GET /api/v1/me returns the person, the workspace, and the organization the token is bound to. It requires the profile:read scope.
A request with an OAuth token can do only what both of the following allow:
- The person’s role in the workspace. If the role lacks a permission, the request fails with
403 PERMISSION_DENIED. - The scopes the person granted. If a scope is missing, the request fails with
403 INSUFFICIENT_SCOPE, and the body listsrequiredScopesandgrantedScopes.
403 PERMISSION_DENIED. The connecting organization needs a plan that includes the API, Team or above, or requests fail with 403 PLAN_REQUIRED. Requests count against the organization’s rate limits, shared with its API keys.
To work in another workspace, send the person through the authorize URL again and let them select it. Each authorization gives you a separate pair of tokens.
Refresh the access token
When the access token expires, API requests fail with401 UNAUTHORIZED. To get a new one, send the refresh token to the token endpoint:
Rotation
Refresh tokens rotate: every refresh returns a new refresh token, and the one you sent stops working at once. There’s no grace period, so follow these rules:- Store the new refresh token before you do anything else with the response. If you lose it, the person has to authorize your app again.
- Refresh from one place at a time. If two refreshes with the same token run at the same time, one succeeds and the other fails with
invalid_grant. Serialize refreshes per token, for example with a lock. - Refresh when you need to, not on every request. The previous access token keeps working until it expires, so you can refresh when a request returns
401or shortly beforeexpires_inruns out.
Revoke access
To disconnect your app, such as when a customer uninstalls it, send the refresh token to the revoke endpoint (RFC 7009):token revokes nothing. The response is always 200 OK with an empty body, even for an unknown token, so it never reveals whether a token exists.
People can also revoke your app themselves, under Inställningar > Appar in the dashboard, and organization members with permission to revoke app access can revoke any app connected to the organization, under Inställningar > Utvecklare > OAuth-appar. After a revocation, API requests return 401 UNAUTHORIZED and refreshes fail with invalid_grant. Send the person through the authorize URL to reconnect.
Token endpoint errors
The token endpoint returns OAuth 2.0 errors, not the error shape of the REST API:
The token endpoint allows 60 requests per minute from one IP address and 600 per minute for one client.
Scopes
A scope limits what a token can do, on top of the person’s role. Request only the scopes your app needs; the consent screen lists each one. Scopes don’t include each other:documents:write doesn’t grant documents:read, so request both if you need both. Each endpoint’s page in the API reference lists the scopes it requires.
signatures:read exposes national identity numbers, such as Swedish personnummer. Request it only if your app needs to know who signed, and handle the data as personal data.
Next steps
- Errors: the error shape of the REST API, including
INSUFFICIENT_SCOPE. - Rate limits and quotas: the limits your app shares with the organization’s API keys.
- Webhooks: get notified of changes instead of polling.

