Signup
The signup API provides an ID-porten verified registration flow. Users authenticate with their Norwegian national identity (personnummer) via ID-porten, select an organization they represent, and complete account creation. The flow supports both new users and existing users adding a new organization.
All signup endpoints are public (no authentication required) and rate-limited.
Endpoints
| Method | Endpoint | Description |
|---|---|---|
| POST | /public/v2/auth/signup/authorize | Initiate ID-porten authorization |
| GET | /public/v2/auth/signup/callback | Handle ID-porten callback (browser-only) |
| POST | /public/v2/auth/signup | Complete signup |
Signup Flow
The signup process is a three-step flow:
- Authorize — Your app calls
POST /public/v2/auth/signup/authorizeto get an ID-porten authorization URL and a session key. - Callback — Open the authorization URL in a popup window. After the user authenticates with ID-porten, the popup posts a message back to your app with the session key, the user’s name, and their authorized organizations.
- Complete — Your app calls
POST /public/v2/auth/signupwith the session key, selected organization, and (for new users) email and password to create the account.
The session is stored server-side with a 15-minute TTL and is single-use.
Initiate Authorization
POST /public/v2/auth/signup/authorize
Returns an ID-porten authorization URL and a session key. Rate limited to 10 requests per hour.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| provider | string | No | Identity provider. Currently only id-porten (default) |
Response
{
"authorization_url": "https://login.idporten.no/authorize?...",
"session_key": "abc123def456"
}
Open authorization_url in a popup window. The session_key is used in subsequent steps to reference the authenticated session.
Error Responses
| Status | Description |
|---|---|
| 400 | Unsupported provider |
| 429 | Rate limit exceeded (10 per hour) |
Handle Callback
GET /public/v2/auth/signup/callback
Handles the OAuth callback from ID-porten. This endpoint is called by the browser as a redirect from ID-porten — it is not called directly by your application. Rate limited to 20 requests per hour.
On success, the endpoint returns an HTML page that posts a window.postMessage to the opener window and closes itself. On failure, it posts an error message instead.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| provider | string | No | Identity provider. Currently only id-porten (default) |
| code | string | Yes | Authorization code from ID-porten (added by the OAuth redirect) |
| state | string | Yes | State parameter from the OAuth redirect |
Success Message
The popup posts a message to window.opener with this shape:
{
"type": "ID_PORTEN_SIGNUP_VERIFIED",
"session_key": "abc123def456",
"given_name": "Ola",
"family_name": "Nordmann",
"is_existing_user": false,
"organizations": [
{
"id": 101,
"name": "Nordmann AS",
"organization_number": "123456789",
"already_registered": false
}
]
}
| Field | Type | Description |
|---|---|---|
| type | string | Always ID_PORTEN_SIGNUP_VERIFIED |
| session_key | string | The session key to use when completing signup |
| given_name | string | User’s first name from ID-porten |
| family_name | string | User’s last name from ID-porten |
| is_existing_user | boolean | Whether the identity matches an existing Snapbooks user |
| organizations | array | Organizations the user can represent via Altinn |
Each organization object:
| Field | Type | Description |
|---|---|---|
| id | integer | Snapbooks organization ID |
| name | string | Organization name |
| organization_number | string | Norwegian organization number |
| already_registered | boolean | Whether this organization already has a Snapbooks client account |
Error Message
{
"type": "ID_PORTEN_SIGNUP_ERROR",
"error": "An unexpected error occurred"
}
Complete Signup
POST /public/v2/auth/signup
Completes the signup by creating a user account (if needed) and a client account for the selected organization. Rate limited to 50 requests per hour.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| session_key | string | Yes | Session key from the callback step |
| organization_id | integer | Yes | ID of the organization to register (must be in the session’s authorized list) |
| string | Conditional | Email address. Required for new users | |
| password | string | Conditional | Password. Required for new users |
For existing users (where is_existing_user was true in the callback message), only session_key and organization_id are required. The user is logged in automatically.
For new users, email and password are also required. If the email belongs to an existing account without an ID-porten identity, the user must provide the correct password to link the identity.
Example Request (New User)
{
"session_key": "abc123def456",
"organization_id": 101,
"email": "ola@nordmann.no",
"password": "securePassword123"
}
Example Request (Existing User)
{
"session_key": "abc123def456",
"organization_id": 101
}
Response
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user": {
"id": 42,
"first_name": "Ola",
"last_name": "Nordmann",
"email": "ola@nordmann.no"
},
"status": true,
"message": "User created successfully"
}
The response includes:
| Field | Type | Description |
|---|---|---|
| token | string | JWT access token |
| user | object | The created or existing user |
| status | boolean | Always true on success |
| message | string | "User created successfully" for new users, "Organization added successfully" for existing users |
A refresh_token cookie is also set as an HTTP-only secure cookie.
Error Responses
| Status | Description |
|---|---|
| 400 | Missing session_key or organization_id |
| 400 | Invalid or expired session (sessions expire after 15 minutes) |
| 400 | Organization not in the session’s authorized list |
| 400 | Organization already registered |
| 400 | Missing email or password for new user |
| 400 | Password is too weak |
| 400 | A user with this email already exists (with a different ID-porten identity) |
| 400 | Incorrect password (when linking to existing email account) |
| 400 | This identity is already registered |
| 429 | Rate limit exceeded (50 per hour) |
Notes
- The signup flow uses ID-porten for identity verification. The user’s personnummer is stored as an HMAC-SHA256 hash for privacy.
- Organizations are fetched from Altinn during the callback step. Only organizations the user can represent (via Altinn authorized parties) are available for selection.
- Organizations that already have a Snapbooks client account cannot be registered again.
- The signup session is single-use — once
POST /public/v2/auth/signupsucceeds, the session is deleted. - After signup, ID-porten tokens are stored to provide immediate Altinn access without requiring the user to authenticate again.
Related Resources
- OAuth2 Authentication — OAuth2 flows for existing users
- Users — user account management
- Client Accounts — client account management
- Integrations — ID-porten and Altinn integration details