Client Accounts

Client accounts represent individual business entities within the system. Each client account has its own settings, users, and business data.

Endpoints

Method Endpoint Description
GET /client-accounts List client accounts
GET /client-accounts/{id} Get a specific client account
POST /client-accounts Create a new client account
PUT/PATCH /client-accounts/{id} Update a client account
DELETE /client-accounts/{id} Delete a client account (complete removal)
POST /client-accounts/{id}/reset Reset a client account (selective data clearing)
POST /client-accounts/{id}/logo Upload the company logo (multipart)
DELETE /client-accounts/{id}/logo Remove the company logo

Query Parameters

The GET /client-accounts endpoint supports the following query parameters:

Parameter Type Required Description
order_by string No Sort order
page integer No Page number for pagination (default: 1)
per_page integer No Number of items per page (default: 100)
has_direct_role boolean No Filter by user’s direct role. Values: true (has direct ClientAccountUser), false (no direct role)
is_provider boolean No Filter by provider status. Values: true (is a provider), false (not a provider)
provider_type string No Filter by specific provider type. Values: ACCOUNTANT, AUDITOR
is_active boolean No Filter by active status. Values: true (active), false (inactive)
client_contracts.provider_client_account_id integer No Only accounts that are the client side of a contract with this provider account. Use an agency’s own client account ID to list its client portfolio
client_contracts.service_provided string No Only accounts whose client contract provides this service. Values: ACCOUNTING, AUDITING, TASK_CONTRIBUTION
client_contracts.is_active boolean No Only accounts whose client contract is active (true) or inactive (false)

The client_contracts.* parameters narrow by the contracts where the listed account is the client (see Contracts). An account is returned when one of its client contracts matches all the client_contracts.* values you sent — they are evaluated against the same contract row, not independently. They never widen the result: accounts you cannot access stay excluded.

Direct Role vs Contract Access

Client accounts can be accessed in two ways:

  1. Direct Role: Users have a direct ClientAccountUser association with the client account. This is typical for regular customers who have direct access to their own company.
  2. Contract Access: Agency users (like accountants and auditors) may have access through a contract between their agency and the client account, without having a direct role.

Filtering Examples

The has_direct_role parameter helps distinguish between these access types:

# Get client accounts where user has a direct role (e.g. customer's own company)
GET /client-accounts?has_direct_role=true

# Get client accounts where user has only contract-based access (e.g. accountant's clients)
GET /client-accounts?has_direct_role=false

# Get all accessible client accounts regardless of access type
GET /client-accounts

# Get only provider accounts (accountants/auditors)
GET /client-accounts?is_provider=true

# Get only accountant accounts
GET /client-accounts?provider_type=ACCOUNTANT

# Get only active accounts
GET /client-accounts?is_active=true

# Combine filters
GET /client-accounts?is_provider=true&provider_type=ACCOUNTANT&is_active=true

# Get an agency's client portfolio: accounts with a contract where account 7 is the provider
GET /client-accounts?client_contracts.provider_client_account_id=7

# Narrow that portfolio to live accounting engagements
GET /client-accounts?client_contracts.provider_client_account_id=7&client_contracts.service_provided=ACCOUNTING&client_contracts.is_active=true

Attributes

Attribute Type Description
id integer The unique identifier of the client account
created_at datetime When the client account was created
created_by_id integer The ID of the user who created the account
updated_at datetime When the account was last updated
updated_by_id integer The ID of the user who last updated the account
unique_name string Unique identifier for the account
display_name string Display name for the account
is_active boolean Whether the account is active
accounting_currency string The main currency used for accounting
accounting_language string The language of auto-generated ledger text (posting memos and journal-line descriptions), as a 3-letter ISO 639-2 code. Permitted values: nob (Norwegian), swe (Swedish), dan (Danish), eng (English). This governs only what is written to the books (per Bokføringsloven §12) — it is independent of the UI and API response language. When omitted on creation, it is derived from the organization’s country (NO → nob, SE → swe, DK → dan, any other or unknown country → eng). Only the Norwegian (nob) text catalog is currently compiled — swe and dan are accepted and stored, but ledger text renders in English until their catalogs are added
organization_id integer The ID of the associated organization
organization_number string The organization number
logo_url string A temporary presigned URL to the company logo, or null if no logo is set. Rendered on invoice/credit-note PDFs. The URL expires after 7 days. Read-only — set via the logo upload endpoint
metadata object Additional metadata for the account
selected_industry_extension string The selected SAF-T industry extension code, or null if none. Read-only
is_provider boolean Whether the account is a provider (accountant/auditor). Read-only
provider_type string Type of provider if applicable (ACCOUNTANT/AUDITOR). Read-only
is_vat_liable boolean Effective VAT liability, derived from the current settings (falling back to the organization). Read-only
is_billing_account boolean Whether other client accounts reference this account as their billing account (e.g. Snapbooks AS and accountant firms). Used by the frontend to bypass add-on gates. Derived and read-only
next_sale_invoice_number integer The number that will be assigned to the next issued sale invoice. Defaults to 10000 for an account that has never issued an invoice. Writable via PUT/PATCH — see Updating Client Accounts
next_sale_credit_note_number integer The number that will be assigned to the next issued sale credit note. Defaults to 10000 for an account that has never issued a credit note. Writable via PUT/PATCH — see Updating Client Accounts

Relationships

Relationship Type Description
organization Organization The associated organization
users [ClientAccountUser] Users with access to this account
provider_contracts [Contract] Contracts where this account is the provider
client_contracts [Contract] Contracts where this account is the client
organizational_roles OrganizationalRole Organizational roles defined for this account
settings ClientAccountSettings Historical settings for this account
current_settings ClientAccountSettings Current active settings
statistics object Statistical information about the account

Example Response

{
    "id": 1,
    "created_at": "2023-05-10T12:00:00Z",
    "created_by_id": 1,
    "updated_at": "2023-05-10T12:00:00Z",
    "updated_by_id": 1,
    "unique_name": "acme-corp",
    "display_name": "ACME Corporation",
    "is_active": true,
    "accounting_currency": "EUR",
    "accounting_language": "nob",
    "organization_id": 1,
    "organization_number": "123456789",
    "logo_url": "https://s3.example.com/client-account-logos/abc123?...signature...",
    "metadata": {
        "industry": "Technology",
        "size": "Medium"
    },
    "selected_industry_extension": null,
    "is_provider": true,
    "provider_type": "ACCOUNTANT",
    "is_vat_liable": true,
    "is_billing_account": false,
    "organization": {
        "id": 1,
        "name": "ACME Corporation"
    },
    "current_settings": {
        "id": 1,
        "active_from": "2023-01-01",
        "plan": "ENTERPRISE",
        "vat_liable": true,
        "project_module": true,
        "bank_integration_module": true
    },
    "statistics": {
        "total_users": 10,
        "active_projects": 5
    }
}

Error Responses

Status Code Description
400 Invalid query parameters
403 User not authorized to access client account
404 Client account not found

Creating Client Accounts

Basic Client Account Creation

Any user can create a basic client account by providing the required organization information:

POST /client-accounts
{
    "organization_id": 12345,
    "display_name": "New Client Company AS",
    "accounting_currency": "NOK"
}

accounting_language is optional on creation — when omitted it is derived from the organization’s country (see the Attributes table). To set it explicitly, include it in the request body:

POST /client-accounts
{
    "organization_id": 12345,
    "display_name": "New Client Company AS",
    "accounting_currency": "NOK",
    "accounting_language": "eng"
}

Supplying an unsupported accounting_language value (anything other than nob, swe, dan, or eng) is rejected with a validation error.

Provider Creating Client Account with Contract

Accountant and auditor providers can create client accounts with contracts in a single request. The contract will be automatically approved since the provider is creating both the client account and the contract:

POST /client-accounts
{
    "organization_id": 12345,
    "display_name": "New Client Company AS",
    "accounting_currency": "NOK",
    "client_contracts": [
        {
            "provider_client_account_id": 123,
            "service_provided": "ACCOUNTING",
            "start_date": "2025-01-01"
        }
    ]
}

Response:

{
    "id": 789,
    "display_name": "New Client Company AS",
    "organization_id": 12345,
    "accounting_currency": "NOK",
    "client_contracts": [
        {
            "id": 456,
            "provider_client_account_id": 123,
            "client_account_id": 789,
            "service_provided": "ACCOUNTING",
            "start_date": "2025-01-01",
            "approval_status": "APPROVED",
            "is_active": true
        }
    ]
}

Authorization Rules

  • Basic creation: Any authenticated user can create client accounts
  • With contracts: Only provider accounts (accountants/auditors) can create client accounts with contracts
  • Organization uniqueness: Each organization can only have one client account
  • Provider validation: Users can only create contracts for their own provider accounts

Error Responses

Status Code Code Description
400 common.validation_error Empty body, unsupported accounting_language, or an organization_number that is not in the business registry
403 common.forbidden Only providers can create a client account with contracts, or the contract names a provider account the caller is not a member of
409 client_account.organization_already_has_client_account The organization already has a client account

Updating Client Accounts

Endpoint: PUT/PATCH /client-accounts/{id}

Updates an existing client account. Only the fields included in the request body are changed; any attribute marked read-only is ignored if sent.

Resetting Document Numbering

next_sale_invoice_number and next_sale_credit_note_number are write-only controls, not stored columns: sending either in the request body resets the underlying counter so the next issued document gets that number.

Request:

PATCH /client-accounts/123
{
    "next_sale_invoice_number": 5000
}

Response:

{
    "id": 123,
    "next_sale_invoice_number": 5000,
    "next_sale_credit_note_number": 10500
}
  • The value must be a positive integer (>= 1); anything else is rejected with a 400.
  • Lowering the number is allowed — this deliberately permits reissuing document numbers that have already been used. The Snapbooks UI warns before saving a decrease, but the API does not block it.
  • The two counters are independent; updating one does not affect the other.

Managing Client Account Users

Client accounts support user management through dedicated endpoints that allow viewing, updating roles, and removing users from client accounts.

Endpoints

Method Endpoint Description
GET /client-accounts/{client_account_id}/users List all users in a client account
PATCH /client-accounts/{client_account_id}/users/{user_id} Update a user’s role
DELETE /client-accounts/{client_account_id}/users/{user_id} Remove a user from the client account

List Users

Get all active users in a client account with their roles and details.

Authorization: Any user with access to the client account can view the user list.

Request:

GET /client-accounts/123/users?with=user,role

The user and role objects are only included when requested via the with query parameter — plain GET /client-accounts/123/users returns just the base fields below.

Response:

[
    {
        "id": 456,
        "user_id": 789,
        "client_account_id": 123,
        "role_id": 3,
        "is_active": true,
        "created_at": "2023-05-10T12:00:00Z",
        "user": {
            "id": 789,
            "email": "user@example.com",
            "first_name": "John",
            "last_name": "Doe"
        },
        "role": {
            "id": 3,
            "name": "CA"
        }
    }
]

Update User Role

Update a user’s role within a client account.

Authorization: Only Accountants (AA) and Client Account Owners (CA) can update user roles.

Business Rules:

  • Cannot change your own role
  • Valid roles: AA (Accountant), CA (Client Account Owner), US (User). Assigning any other role (including SA, BK, or EM) is rejected

Request:

PATCH /client-accounts/123/users/789?with=user,role
Content-Type: application/json

{
    "role_id": 2
}

Response:

{
    "id": 456,
    "user_id": 789,
    "client_account_id": 123,
    "role_id": 2,
    "is_active": true,
    "created_at": "2023-05-10T12:00:00Z",
    "user": {
        "id": 789,
        "email": "user@example.com",
        "first_name": "John",
        "last_name": "Doe"
    },
    "role": {
        "id": 2,
        "name": "AA"
    }
}

Remove User

Remove a user from a client account (soft delete - sets is_active to false).

Authorization: Only Accountants (AA) and Client Account Owners (CA) can remove users.

Business Rules:

  • Cannot remove yourself from the client account
  • Cannot remove the last Client Account Owner (CA)
  • User is soft-deleted (is_active set to false) but remains in the database

Request:

DELETE /client-accounts/123/users/789

Response:

{
    "id": 456,
    "user_id": 789,
    "client_account_id": 123,
    "role_id": 3,
    "is_active": false,
    "created_at": "2023-05-10T12:00:00Z"
}

Error Responses

Status Code Code Description
400 common.validation_error Missing or invalid request body, a role other than AA, CA, or US, or a user that is not in this client account
403 common.forbidden No access to the client account, or the caller is not an Accountant or Client Account Owner
403 client_account.cannot_change_own_role The user_id in the path is the caller’s own user ID
403 client_account.cannot_remove_self The caller tried to remove themselves from the client account
409 client_account.cannot_remove_last_owner Removing the user would leave the account without an active Client Account Owner

See Client Account Users for the full reference on these endpoints.

Role Types

Code Name Description
SA System Administrator Cannot be assigned via API
AA Accountant Full access, can manage users
CA Client Account Owner Full access, can manage users
BK Bookkeeper Standard bookkeeping access
EM Employee Limited access
US User Assignable via the update-role endpoint; cannot manage users

Each client account can have a single company logo. The logo is rendered on invoice and credit-note PDFs. The current logo is exposed on the client account as the read-only logo_url attribute (a temporary presigned URL, or null when no logo is set).

Authorization: Any user with access to the client account can upload or remove its logo.

Upload (or replace) the company logo. The request must be multipart/form-data with the image in a file field. Any previously stored logo is replaced.

Endpoint: POST /client-accounts/{id}/logo

Constraints:

  • Accepted content types: JPEG, PNG, and WebP
  • Maximum file size: 2 MB
  • The file content is validated to ensure it matches a real image format

Request:

POST /client-accounts/123/logo
Content-Type: multipart/form-data; boundary=----boundary

------boundary
Content-Disposition: form-data; name="file"; filename="logo.png"
Content-Type: image/png

<binary image data>
------boundary--

Response:

Returns the updated client account object (see Example Response above), with logo_url now pointing at the newly uploaded image.

{
    "id": 123,
    "display_name": "ACME Corporation",
    "logo_url": "https://s3.example.com/client-account-logos/abc123?...signature...",
    ...
}

Remove the company logo. After removal, logo_url is null.

Endpoint: DELETE /client-accounts/{id}/logo

Request:

DELETE /client-accounts/123/logo

Response:

Returns the updated client account object with logo_url set to null.

Error Responses

Status Code Code Description
400 common.validation_error Missing file field or no file selected
400 client_account.invalid_logo_format The content type is not JPEG, PNG, or WebP, or the bytes do not match a real image
400 client_account.logo_too_large The file is larger than 2 MB
403 common.forbidden User not authorized to access this client account
404 common.not_found Client account not found

Deleting and Resetting Client Accounts

Client accounts can be either completely deleted or selectively reset to clear data while preserving the account structure.

Authorization

Both delete and reset operations require elevated permissions:

  • AA (Accountant, role_id 2): Can delete/reset their own client accounts
  • CA (Client Account Owner, role_id 3): Can delete/reset their own client accounts
  • Other roles: Cannot delete or reset client accounts

Confirmation Requirement

All delete and reset operations require a confirmation token to prevent accidental data loss. The confirmation token must match the client account’s unique_name.

Delete Client Account

Complete removal of a client account and ALL associated data. This operation is irreversible.

Endpoint: DELETE /client-accounts/{id}

Request:

DELETE /client-accounts/123
Content-Type: application/json

{
    "confirm": "acme-corp",
    "dry_run": false
}

What Gets Deleted:

  • All transactional data (documents, journal entries, bank transactions, etc.)
  • All master data (business partners, items, bank accounts, projects, etc.)
  • All integration data (Altinn, Klippa, Zdata, etc.)
  • All settings and configurations
  • All user associations (ClientAccountUser records)

What Is Preserved:

  • Contracts (maintained for both parties’ records)

Response (Success):

{
    "success": true,
    "operation": "DELETE",
    "client_account_id": 123,
    "dry_run": false,
    "deleted_counts": {
        "documents": 450,
        "journal_entries": 1200,
        "business_partners": 35,
        "items": 120,
        "bank_transactions": 890,
        "client_account_users": 5,
        "contracts_as_provider": 0,
        "contracts_as_client": 1,
        "client_account": 1
    },
    "message": "Client account deleted successfully"
}

Dry Run Mode:

Use "dry_run": true to see what would be deleted without actually deleting:

DELETE /client-accounts/123
Content-Type: application/json

{
    "confirm": "acme-corp",
    "dry_run": true
}

The response will include all counts but no data will be deleted, and the message will indicate it was a dry run.

Reset Client Account

Selectively clear data from a client account while preserving the account structure. Choose from three reset levels.

Endpoint: POST /client-accounts/{id}/reset

Reset Levels:

Level Description What Gets Deleted What Gets Preserved
TRANSACTIONAL Clear transaction data only Documents, journal entries, bank transactions, reconciliations, vouchers, posted payments, payment requests, VAT returns, uploaded files, tasks Master data (business partners, items, bank accounts, projects, departments, tags, assets), settings, user associations, contracts
MASTER_DATA Clear transactions + master data Everything from TRANSACTIONAL plus: business partners, items, bank accounts, projects, departments, tags, assets, equity transactions Account structure, settings, user associations, contracts, integration configurations
FULL Clear almost everything Everything from MASTER_DATA plus: settings, organizational roles, integration data (Altinn, Bank, Zdata), chat channels Account shell, user associations, contracts

Request:

POST /client-accounts/123/reset
Content-Type: application/json

{
    "level": "MASTER_DATA",
    "confirm": "acme-corp",
    "dry_run": false
}

Response (Success):

{
    "success": true,
    "operation": "RESET",
    "level": "MASTER_DATA",
    "client_account_id": 123,
    "dry_run": false,
    "reset_counts": {
        "documents": 450,
        "journal_entries": 1200,
        "business_partners": 35,
        "items": 120,
        "bank_accounts": 5,
        "bank_transactions": 890,
        "projects": 8,
        "departments": 3,
        "tags": 12,
        "assets": 15
    },
    "message": "Client account reset successfully at MASTER_DATA level"
}

Dry Run Mode:

Use "dry_run": true to preview what would be reset:

POST /client-accounts/123/reset
Content-Type: application/json

{
    "level": "TRANSACTIONAL",
    "confirm": "acme-corp",
    "dry_run": true
}

Sequence Number Reset

Reset operations also clear sequence numbers to their initial state:

  • TRANSACTIONAL level: Resets transaction-related sequence numbers (documents, vouchers, reconciliations, receipts, payments)
  • MASTER_DATA level: Additionally resets master data sequence numbers (items, business partners, bank accounts, projects, departments, assets, tags)
  • FULL level: Resets all sequence numbers

Error Responses

Status Code Code Description
400 common.validation_error Invalid request body, a missing confirm token, an invalid reset level, or an active contract with another provider blocking the operation
400 client_account.confirmation_mismatch The confirm token does not match the client account’s unique_name
403 common.forbidden The caller has no access to this client account, or holds a role that may not delete/reset it (see Authorization — AA and CA may). An unknown client account ID also answers 403

Example Error Response:

{
    "error": {
        "code": "client_account.confirmation_mismatch",
        "message": "Confirmation token 'acme' does not match client account unique_name",
        "request_id": "b3c1a7e2-4f0d-4c11-9a56-8f2f6f9d1a2b"
    }
}

See Error Handling for the error envelope.

Best Practices

  1. Always use dry run first: Run with "dry_run": true to preview what will be affected
  2. Verify counts: Review the returned counts in dry run mode before proceeding
  3. Backup data: Ensure you have backups before performing destructive operations
  4. Choose appropriate level: Start with TRANSACTIONAL reset if you only need to clear transaction data
  5. Communicate with users: Inform other users before deleting or resetting shared accounts
  6. Test in development: Test delete/reset operations in a development environment first

Common Use Cases

Scenario Recommended Action
Start new accounting year TRANSACTIONAL reset
Clear test data from demo account MASTER_DATA reset
Repurpose existing account FULL reset
Completely remove account DELETE
Remove customer who left DELETE

Notes

  • Only users with appropriate permissions can access a client account.
  • The unique_name must be unique across all client accounts.
  • Each organization can only have one client account - attempts to create duplicate accounts will be rejected.
  • When updating a client account, only certain fields can be modified (unique_name, display_name, is_active, accounting_currency, accounting_language, metadata).
  • Contracts created during client account creation are automatically approved.
  • The created_by_id/updated_by_id is automatically set to the authenticated user’s ID.
  • All dates are returned in ISO 8601 format.
  • Settings are versioned with active_from/active_to dates to track changes over time.