Payroll Contracts API
Employment contracts represent the formal employment agreements between a company and its employees. Each contract captures salary details, working arrangements, pension configuration, and is used as the basis for payroll runs and A-melding reporting to Norwegian tax authorities.
API Endpoints
| Method | Endpoint | Description |
|---|---|---|
| GET | /payroll/contracts | List employment contracts |
| GET | /payroll/contracts/{id} | Get an employment contract |
| POST | /payroll/contracts | Create an employment contract |
| POST/PUT | /payroll/contracts/{id} | Update an employment contract |
List Employment Contracts
GET /payroll/contracts
Retrieve a paginated list of employment contracts with filtering options.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| client_account_id | integer | Yes | Filter by client account ID (must be from user’s eligible accounts) |
| business_partner_id | integer | No | Filter by employee (business partner) ID |
| is_active | boolean | No | Filter by active status |
| page | integer | No | Page number for pagination (default: 1) |
| per_page | integer | No | Number of items per page (default: 100) |
| with | string | No | Comma-separated list of relations to include |
Response
{
"data": [
{
"id": 1,
"business_partner_id": 56,
"client_account_id": 7,
"employment_type": "EMPLOYED",
"start_date": "2024-01-15",
"end_date": null,
"job_title": "Accountant",
"occupation_code_lv": "2511102",
"employment_form": "PERMANENT",
"work_time_arrangement_lv": "ikkeSkift",
"work_percentage": "100.00",
"weekly_hours": "37.50",
"salary_type": "MONTHLY_FIXED",
"monthly_salary": "45000.000000",
"hourly_rate": null,
"vacation_pay_rate": "0.1200",
"pension_rate": "0.0200",
"employee_pension_rate": "0.0200",
"pension_provider_id": null,
"union_dues_amount": null,
"car_registration_number": null,
"car_list_price": null,
"is_primary_employer": true,
"is_active": true,
"is_payroll_default": true,
"arbeidsforhold_id": "a1b2c3d4e5f6g7h8i9j0",
"created_by_id": 7,
"created_at": "2024-01-15T10:00:00Z"
}
],
"meta": {
"page": 1,
"pages": 1,
"per_page": 100,
"records": 1
}
}
Get Employment Contract
GET /payroll/contracts/{id}
Retrieve a single employment contract by ID.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| id | integer | Contract ID |
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| with | string | No | Comma-separated list of relations to include |
Create Employment Contract
POST /payroll/contracts
Creates a new employment contract. Automatically triggers a background task to fetch tax cards from Skatteetaten for the current tax year.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| client_account_id | integer | Yes | The client account ID |
| business_partner_id | integer | Yes | The employee (business partner) ID |
| employment_type | string | Yes | EMPLOYED, MARITIME, FREELANCER or PENSIONER |
| start_date | date | Yes | Employment start date |
| salary_type | string | Yes | MONTHLY_FIXED, HOURLY or COMMISSION (no base pay; each run gets COMMISSION lines) |
| monthly_salary | decimal | Conditional | Required when salary_type is MONTHLY_FIXED |
| hourly_rate | decimal | Conditional | Required when salary_type is HOURLY |
| end_date | date | No | Employment end date |
| end_date_reason | string | No | Reason for end date (max 50 characters) |
| job_title | string | No | Job title (max 200 characters) |
| occupation_code_lv | string | No | 7-digit occupation code from SSB’s yrkeskatalog, stored verbatim |
| employment_form | string | No | PERMANENT or TEMPORARY |
| work_time_arrangement_lv | string | No | Skatteetaten’s arbeidstidsordning code, stored verbatim (see below) |
| work_percentage | decimal | No | Agreed position in percent, 0–100 (e.g. 100.00 for full-time) |
| weekly_hours | decimal | No | Hours per week in a full-time position at the workplace (normally 37.5), not the employee’s own hours; above 0 and at most 168 |
| remuneration_type | string | No | Remuneration type (max 50 characters); not reported in the a-melding, which dropped avloenningstype in 2019 |
| last_salary_change_date | date | No | Date of last salary change |
| last_work_pct_change_date | date | No | Date of last work percentage change |
| vacation_pay_rate | decimal | No | Vacation pay rate as a fraction (e.g. 0.1200 for 12%) |
| pension_rate | decimal | No | Employer pension rate as a fraction (e.g. 0.0200 for 2%) |
| employee_pension_rate | decimal | No | Employee pension rate as a fraction |
| pension_provider_id | integer | No | Pension provider business partner ID |
| union_dues_amount | decimal | No | Monthly union dues amount |
| car_registration_number | string | No | Vehicle registration number for company car benefit (max 20 characters) |
| car_list_price | decimal | No | List price of company car (used for a-melding reporting to Skatteetaten) |
| is_primary_employer | boolean | No | Whether this is the primary employer (default: true) |
| is_active | boolean | No | Whether the contract is active (default: true) |
| is_payroll_default | boolean | No | Whether this is the default contract for payroll (default: false) |
Example Request
{
"client_account_id": 7,
"business_partner_id": 56,
"employment_type": "EMPLOYED",
"start_date": "2024-01-15",
"salary_type": "MONTHLY_FIXED",
"monthly_salary": "45000.00",
"job_title": "Accountant",
"occupation_code_lv": "2511102",
"employment_form": "PERMANENT",
"work_time_arrangement_lv": "ikkeSkift",
"work_percentage": "100.00",
"weekly_hours": "37.50",
"vacation_pay_rate": "0.1200",
"pension_rate": "0.0200",
"is_primary_employer": true,
"is_payroll_default": true
}
Local values
Fields ending in _lv hold a code from a national code list exactly as the authority writes it, rather than a Snapbooks enum:
occupation_code_lv: the 7-digit code from SSB’s yrkeskatalog, e.g.2511102.work_time_arrangement_lv: one of Skatteetaten’s arbeidstidsordning codes:ikkeSkift,andreSkift,skift365,doegnkontinuerligSkiftOgTurnus355,helkontinuerligSkiftOgAndreOrdninger336oroffshore336.
When a create or update request includes job_title for an EMPLOYED contract that has no occupation_code_lv, Snapbooks looks the code up from the title, the company’s industry and its size before saving. This adds a few seconds to that request. A code that is already set is never replaced; to look it up again, send occupation_code_lv: null together with job_title. If no code fits, the contract is saved without one.
An EMPLOYED contract is reported to Skatteetaten as an ordinary arbeidsforhold, which needs occupation_code_lv, work_percentage, weekly_hours, work_time_arrangement_lv, employment_form and last_salary_change_date. Without them the a-melding is accepted but flagged for correction.
Response
Returns the created employment contract with status 201 Created.
Reference Ownership
business_partner_id and pension_provider_id are taken from the request body, so both are checked against the client account before the contract is stored. Each must name a business partner owned by client_account_id; anything else fails with 400 Bad Request. An id that does not exist is rejected the same way as one owned by another client account, so the response cannot be used to probe for existing ids.
Update Employment Contract
POST/PUT /payroll/contracts/{id}
Updates an existing employment contract. Both POST and PUT methods are accepted. Only provided fields are updated.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| id | integer | Contract ID |
Request Body
Same fields as create, except client_account_id and business_partner_id which cannot be changed after creation.
pension_provider_id is re-checked against the ownership rule above only when the field is present in the request body. A contract that already carries a pension provider you are not entitled to can therefore still be edited or deactivated without first correcting that field.
Side Effects
- Setting
is_activefromfalsetotruetriggers a background task to fetch tax cards. - Setting
is_activefromtruetofalsetriggers an A-melding submission to Skatteetaten with the end date.
Response
Returns the updated employment contract.
Attributes
| Attribute | Type | Description |
|---|---|---|
| id | integer | Unique identifier (read-only) |
| business_partner_id | integer | Employee (business partner) ID |
| client_account_id | integer | Client account ID |
| employment_type | string | EMPLOYED, MARITIME, FREELANCER or PENSIONER |
| start_date | date | Employment start date |
| end_date | date | Employment end date (null if ongoing) |
| end_date_reason | string | Reason for end date |
| job_title | string | Job title |
| occupation_code_lv | string | SSB occupation code (local value) |
| occupation_name | string | SSB’s name for the occupation code (read-only) |
| employment_form | string | PERMANENT or TEMPORARY |
| work_time_arrangement_lv | string | Skatteetaten arbeidstidsordning code (local value) |
| work_percentage | decimal | Work percentage (e.g. 100.00) |
| weekly_hours | decimal | Weekly working hours |
| remuneration_type | string | Remuneration type |
| salary_type | string | MONTHLY_FIXED, HOURLY or COMMISSION |
| last_salary_change_date | date | Date of last salary change |
| last_work_pct_change_date | date | Date of last work percentage change |
| monthly_salary | decimal | Monthly salary amount |
| hourly_rate | decimal | Hourly rate amount |
| vacation_pay_rate | decimal | Vacation pay rate |
| pension_rate | decimal | Employer pension rate |
| employee_pension_rate | decimal | Employee pension rate |
| pension_provider_id | integer | Pension provider business partner ID |
| union_dues_amount | decimal | Monthly union dues amount |
| car_registration_number | string | Vehicle registration number for company car benefit |
| car_list_price | decimal | List price of company car (used for a-melding bil tilleggsinformasjon) |
| arbeidsforhold_id | string | Employment relationship ID (auto-generated UUID) |
| is_primary_employer | boolean | Whether this is the primary employer |
| is_active | boolean | Whether the contract is active |
| is_payroll_default | boolean | Whether this is the default contract for payroll |
| created_by_id | integer | ID of the creating user (read-only) |
| created_at | datetime | Creation timestamp (read-only) |
Relationships
The following related objects can be included using the with parameter:
business_partner- The employee associated with the contract
Validation Rules
business_partner_idis required, and must name a business partner in the same client accountclient_account_idis requiredpension_provider_id, if provided, must name a business partner in the same client accountemployment_typeis requiredstart_dateis requiredsalary_typemust beMONTHLY_FIXED,HOURLYorCOMMISSION- When
salary_typeisMONTHLY_FIXED,monthly_salaryis required - When
salary_typeisHOURLY,hourly_rateis required - When
salary_typeisCOMMISSION,monthly_salaryandhourly_ratemust be empty vacation_pay_rate,pension_rateandemployee_pension_rateare fractions below 1work_percentageis between 0 and 100;weekly_hoursis above 0 and at most 168
Error Responses
| Status Code | Description |
|---|---|
| 400 | Validation error (missing or invalid fields, or a business_partner_id/pension_provider_id that does not belong to this client account) |
| 403 | Forbidden (no access to client account) |
| 404 | Contract not found |