The payment report framework is being updated to better meet requirements for functionality and security. Learn about the changes here.

Developer Guide

Skilagrein — Implementation Guide for Collectors and Payroll System Developers

This developer guide is for:

  • collectors (innheimtuaðilar) who receive fund payment submissions from payroll systems and maintain the web services used for communication with payroll systems.
  • payroll system developers who send fund payment submissions to collectors and maintain integrations with skilagrein.is and collector web services.

1. Overview

This guide applies to version 2.0, which is based on a JSON REST API and replaces the legacy XML format (version 1.0). The legacy format will be phased out by collectors following this upgrade.

A collector is responsible for:

  • Exposing a well-known discovery endpoint so payroll systems can discover APIs automatically.
  • Implementing an API to receive and process submissions.
  • Issuing OAuth 2.0 client credentials to employers who are approved users of the collector.
  • Returning standardised responses so payroll systems can handle results consistently.

The two OpenAPI specifications are:

SpecPurpose
service-discovery.yamlDiscovery — tells payroll systems where the API lives and how to authenticate
fund-submissions.yamlCore API — receiving, validating, and acknowledging fund payment submissions

2. Architecture

Payroll System                            Collector
     │                                        │
     │  GET /.well-known/skilagrein-config    │
     │ ─────────────────────────────────────► │  (no auth required)
     │ ◄───────────────────────────────────── │
     │  { endpoint, tokenUrl, scope, ... }    │
     │                                        │
     │  POST {tokenUrl}                       │
     │  client_credentials grant             │
     │ ─────────────────────────────────────► │  Auth Server
     │ ◄───────────────────────────────────── │
     │  { access_token, expires_in, ... }     │
     │                                        │
     │  POST {endpoint}/fund-payments         │
     │  Authorization: Bearer <token>         │
     │ ─────────────────────────────────────► │
     │ ◄───────────────────────────────────── │
     │  201 / 400 / 422 + FundPaymentResponse │

3. Discovery Endpoint — Well-Known Configuration

Payroll systems locate the API by fetching a well-known configuration document. A collector must expose this endpoint at:

GET /.well-known/skilagrein-configuration

This endpoint must be publicly accessible — no authentication required.

3.1 Required Response Fields

FieldTypeRequiredDescription
schemaVersionstringVersion of the configuration schema (currently "1.0")
collectorIdstringThe collector's SAL number
apiVersionsarrayAt least one API version entry (see below)

Each entry in apiVersions must include:

FieldTypeRequiredDescription
apiVersionstringAPI version string, e.g. "1.0"
validFromdateDate this version became active
validTodate/nullExpiry date, null if still active
endpointURIBase URL of the Fund Payments API
validationEndpointURIURL of the optional validation endpoint for this API version. Omit if not supported
openApiUrlURIURL to the collector's published OpenAPI spec
authenticationobjectOAuth configuration (see Section 4)

3.2 Example Response

{
  "schemaVersion": "1.0",
  "collectorId": "1234",
  "apiVersions": [
    {
      "apiVersion": "1.0",
      "validFrom": "2026-01-01",
      "validTo": null,
      "endpoint": "https://api.example.is/v1/fund-payments",
      "validationEndpoint": "https://api.example.is/v1/fund-payments/validation",
      "openApiUrl": "https://api.example.is/v1/openapi.json",
      "authentication": {
        "type": "oauth2_client_credentials",
        "tokenUrl": "https://auth.example.is/connect/token",
        "scope": "skilagrein",
        "credentialContact": {
          "description": "Contact us to obtain OAuth client credentials (client ID and secret)",
          "email": "api@example.is",
          "url": "https://developer.example.is/access"
        }
      }
    }
  ]
}

Note: If the validation endpoint is not implemented, simply omit the validationEndpoint field from the corresponding apiVersions entry.


4. Authentication

The technical specification offers three authentication methods: [oauth, basic, none]. It is up to the fund collector to decide what they use.

It is strongly recommended that all calls to the Fund Payments API be authenticated using OAuth 2.0 Client Credentials (client_credentials grant). If basic (user+pass) then it should be put in the header.

This section describes what a collector must implement and what the flow looks like from a payroll system's perspective, given that the collector uses OAuth 2.0 Client Credentials (client_credentials grant)

4.1 What the Collector Must Implement

The collector acts as both the resource server and the authority issuing client credentials to employers (payroll systems). A collector may run its own authorization server or delegate to an identity provider, but the external interface must conform to the standard client_credentials grant.

The authorization server must:

  • Accept a POST request to the tokenUrl published in the well-known configuration.
  • Support the client_credentials grant type.
  • Require a scope value of skilagrein (or another scope defined and published by the collector).
  • Return a standard OAuth 2.0 token response including access_token and expires_in.
  • Issue tokens as Bearer tokens (typically JWT, but the format is up to the collector) that the Fund Payments API can validate on every request.

The Fund Payments API must:

  • Reject requests that are missing an Authorization header with a 401 Unauthorized response.
  • Reject expired or invalid tokens with a 401 Unauthorized response.
  • Reject valid tokens that lack the required scope with a 403 Forbidden response.
  • Accept requests with a valid Bearer token.

4.2 Credential Issuance

Payroll systems read the credentialContact in the well-known document and contact the collector to request API access. The process should cover:

  • Verifying the identity of the requesting operator.
  • Issuing a client_id and client_secret pair.
  • Communicating the tokenUrl and scope to be used.

The credentialContact object in the well-known configuration should always include at minimum a description field. Including email and/or url is also recommended:

"credentialContact": {
  "description": "Contact us to obtain OAuth client credentials (client ID and secret)",
  "email": "api@example.is",
  "url": "https://developer.example.is/access"
}

4.3 Token Request (Payroll System Perspective)

Payroll systems obtain a token by sending a standard client credentials request to the tokenUrl:

POST https://auth.example.is/connect/token
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials
&client_id=<client_id>
&client_secret=<client_secret>
&scope=skilagrein

The server should respond with:

{
  "access_token": "eyJhbGci...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "skilagrein"
}

4.4 Authenticated API Calls

Payroll systems include the access token as a Bearer token on every API call:

POST https://api.example.is/v1/fund-payments
Authorization: Bearer eyJhbGci...
Content-Type: application/json

{ ... }

4.5 Token Expiry and Renewal

  • Tokens expire after expires_in seconds. Payroll systems are expected to request a new token before the current one expires.
  • The API should return 401 Unauthorized for expired tokens. Payroll systems will re-authenticate automatically on receiving a 401.
  • Do not return 200 OK with an error body for authentication failures — the correct HTTP status code should be used instead.

4.6 Security Recommendations

  • Use HTTPS exclusively — reject plain HTTP connections.
  • Use short-lived access tokens (5 minutes is a common default).
  • Scope tokens tightly — the skilagrein scope should grant only the permissions needed for submission.
  • Have a process in place to rotate client credentials quickly if a secret is compromised, and provide operators with a self-service or contact mechanism for rotation.

5. Fund Payments API

5.1 POST /fund-payments — Submit

Submit a fund payment declaration (skilagrein) for processing.

POST {endpoint}/fund-payments
Authorization: Bearer <token>
Content-Type: application/json

HTTP Status Codes

StatusMeaning
201 CreatedSubmission received. See response field in body for outcome (ACCEPTED or ACCEPTED_WITH_COMMENTS).
400 Bad RequestMalformed request — structural or format error before business validation.
422 Unprocessable EntitySubmission rejected due to validation errors. See issues array in response.

Note: A 201 does not automatically mean the submission was fully accepted — the response field in the body must always be checked.


5.2 POST /fund-payments/validation — Validate (Optional)

Validation endpoint (dry-run): validates the submission without processing it or posting entitlements. Useful for payroll systems to check for errors before an actual submission is made.

This endpoint is optional. If implemented, the validationEndpoint is included in the well-known configuration.

The request and response structure is identical to POST /fund-payments.


5.3 DELETE /fund-payments/{transactionId} — Reverse a Submission

Reverse (cancel) a previously submitted fund payment in full by transactionId. This replaces sending a negative (mirroring) submission to undo a whole submission.

DELETE {endpoint}/fund-payments/{transactionId}
Authorization: Bearer <token>

Funds may impose their own business rules on whether a reversal is allowed (e.g. once a fund payment has been booked, reversal may be rejected). For partial reversal, send a correcting (negative) submission via POST /fund-payments.

HTTP Status Codes

StatusMeaning
200 OKFund payment was reversed. response field in body is REVERSED.
403 ForbiddenCaller is not authorized to reverse this fund payment.
404 Not FoundNo fund payment exists for the provided transactionId.
409 ConflictFund payment cannot be reversed (already booked or already reversed). See issues in body for details.

5.4 GET /fund-entities — Supported Funds

Returns the list of funds the collector collects for, along with the entity types, default ratios (percentages) and dynamic additional fields (additionalAttributes) available for each fund.

GET {endpoint}/fund-entities
Authorization: Bearer <token>

Payroll systems can use this information to update fund-specific settings.

Fund-specific fields (additionalAttributes)

Instead of fixed, fund-specific fields for B-department funds (or other special funds), a general, dynamic mechanism is used: each fund declares in entityTypeRules[].additionalAttributes which extra fields it accepts and how they are validated. Payroll systems carry the values of those fields on paymentEntry.additionalAttributes as { name, value } pairs.

This is intentionally general — it is not B-department-specific. Other associations or funds can later declare their own additional fields (e.g. due to new collective agreements) without changes to the spec.

Each rule in additionalAttributes has the following fields:

FieldTypeRequiredDescription
namestringAttribute name. Must match the name used on paymentEntry.additionalAttributes.
typeenumLogical type of the value: string, number, boolean or date. The collector parses the string accordingly.
descriptionstringHuman-readable description, suitable for display in a payroll system.
requiredbooleanWhether the attribute must be present on every matching paymentEntry. Defaults to false.
allowedValuesarrayOptional closed set of allowed values. When present, value must be one of them.

Example Response

[
  {
    "entityNo": "1005",
    "entityTypeRules": [
      {
        "entityType": "L",
        "defaultPercentage": 0.12,
        "additionalAttributes": [
          { "name": "salarySymbol", "type": "string", "description":"Launatákn", "allowedValues": ["001", "B", "V", "032"] }
        ]
      },
      {
        "entityType": "F",
        "defaultPercentage": 0.01,
        "additionalAttributes": [
          { "name": "daysAtSea", "type": "number", "description": "Dagar á sjó" }
        ]
      }
    ]
  }
]

Example: B-department fund

A B-department fund can declare the following fund-specific fields on its entity type — including fields that previously were fixed in the spec:

{
  "entityNo": "1234",
  "entityTypeRules": [
    {
      "entityType": "L",
      "defaultPercentage": 0.155,
      "additionalAttributes": [
        { "name": "salarySymbol",      "type": "string", "required": true,  "allowedValues": ["001", "B", "V", "032"], "description": "Salary symbol" },
        { "name": "employmentRatio",   "type": "number", "required": true,  "description": "Employment ratio" },
        { "name": "salaryTable",       "type": "string", "description": "Salary table" },
        { "name": "salaryCategory",    "type": "string", "description": "Salary category" },
        { "name": "salarySubCategory", "type": "string", "description": "Salary subcategory" },
        { "name": "additionalAmount",  "type": "number", "description": "Tied private savings (bundin séreign)" }
      ]
    }
  ]
}

The payroll system sends matching values on paymentEntry.additionalAttributes:

{
  "entityType": "L",
  "entityNo": "1234",
  "amount": 50000,
  "amountPayrollPercentage": 0.155,
  "additionalAttributes": [
    { "name": "salarySymbol",      "value": "001" },
    { "name": "employmentRatio",   "value": "1.0" },
    { "name": "salaryTable",       "value": "A" },
    { "name": "salaryCategory",    "value": "CAT1" },
    { "name": "salarySubCategory", "value": "SUB1" },
    { "name": "additionalAmount",  "value": "0" }
  ]
}

Validation rules for additional fields (see also section 8):

  • A field marked required: true must be present on every matching paymentEntry.
  • value must be parseable as the declared type (e.g. a number field must hold a valid number).
  • When allowedValues is set, value must be one of those values.
  • Fields not declared in the fund's additionalAttributes rules should be ignored or raise a warning.

6. Request Structure Reference

6.1 Top-Level: FundPaymentSubmission

FieldTypeRequiredDescription
transactionIdstringUnique ID assigned by a payroll system for this submission. Used to correlate responses.
employerNationalIdstringEmployer kennitala — exactly 10 digits, no hyphen.
currencystringISO 4217 currency code (3 uppercase letters). Default is ISK. For ISK, amounts are integers (no decimal places).
paymentEntryGroupsarrayOne group per employee. At least 1 item.
summariesarrayTotals per fund and entity type.

6.2 PaymentEntryGroup

FieldTypeRequiredDescription
nationalIdstringEmployee kennitala — exactly 10 digits, no hyphen.
employeeTransactionRefstringUnique reference for this employee within the submission. Returned in issue messages to identify errors without exposing the employee's national ID in responses.
periodFromdateStart date of pay period (YYYY-MM-DD), belongs to the period being reported.
periodTodateEnd date of pay period (YYYY-MM-DD), belongs to the period being reported.
paymentEntriesarrayPayment entries.

6.3 PaymentEntry

Represents payment entries for each employee broken down by entity type. Note that the entity type has been split so that the employee and employer contributions are separated into two entries, each with its own entityType, whereas the old model combined these into a single line.

FieldTypeRequiredDescription
entityTypestringShort identifier for the type of entity (e.g. L1 for employee pension contribution, F1 for employee union due, etc.). Use GET /fund-entities to retrieve valid entity types.
entityNostringUnique fund number (SAL).
amountnumberAmount in the declared currency.
amountPayrollPercentagenumberAmount as a percentage of the payroll base (decimal, e.g. 0.12 = 12%).
datedatePayment date. Optional.
additionalAttributesarrayDynamic fund-specific fields as { name, value } pairs (e.g. salarySymbol, employmentRatio, daysAtSea, B-department fields). The fund declares which fields it supports and their validation rules via entityTypeRules[].additionalAttributes returned from GET /fund-entities. See section 5.4 for a B-department example.

6.4 Summary

Contribution sums are aggregated in these fields by entity type and fund number, for cross-checking of totals.

FieldTypeRequiredDescription
entityNostringFund number (SAL).
entityTypestringEntity type identifier.
amountSumnumberSum of all amount values across all entries for this entityNo + entityType combination.
entityNamestringHuman-readable fund name. Optional.

7. Response Structure Reference

7.1 FundPaymentResponse

Response in POST /fund-payments, POST /fund-payments/validation and DELETE /fund-payments/{transactionId} calls.

FieldTypeRequiredDescription
transactionIdstringEchoes back the transactionId from the submission.
responseIdstringThe collector's unique reference for this response.
responseenumOutcome — see values below.
postedAmountnumberTotal amount posted.
issuesarrayEmpty when ACCEPTED. Contains warnings or errors otherwise.

7.2 Response Outcomes

response valueHTTP StatusMeaning
ACCEPTED201Submission fully accepted. issues array is empty.
ACCEPTED_WITH_COMMENTS201Accepted but with warnings or informational comments. Check issues.
REJECTED422Submission rejected. issues contains the errors.
REVERSED200Submission was reversed in full via DELETE /fund-payments/{transactionId}.

7.3 Issue

FieldTypeRequiredDescription
severityenum"warning" or "error"
messagestringHuman-readable description of the issue.
employeeTransactionRefstringReference to the affected employee entry. Present when the issue is tied to a specific employee line. Should not contain the employee's national ID.
entityTypestringEntity type involved in the issue, if applicable.
entityNostringFund number involved in the issue, if applicable.

Design note: employeeTransactionRef is used deliberately instead of nationalId to avoid transmitting personal data in error messages. The quality of error messages depends on whether the payroll system included an employeeTransactionRef in the submission.


8. Validation Rules

These guidelines serve as minimum requirements for validation of fund payment submissions. Each collector and fund may add further rules that apply during validation and/or during receipt and posting of submissions.

Structural

  • employerNationalId must be exactly 10 digits, no hyphen.
  • nationalId (employee) must be exactly 10 digits, no hyphen.
  • currency must be a valid ISO 4217 3-letter code.
  • periodFrom must be before or equal to periodTo.
  • paymentEntries must contain at least 1 entry.
  • paymentEntryGroups must contain at least 1 entry.

Business Rules

  • Both periodFrom and periodTo must fall within the same reporting period.
  • Pay periods spanning a year boundary are generally not permitted — validation must follow the collector's own rules.
  • amountPayrollPercentage should match the expected percentage for the entityType as defined in the collector's GET /fund-entities response.
  • summaries totals must match the sum of the corresponding entries across all groups.
  • entityType and entityNo combinations submitted must be in the set that the collector advertises via GET /fund-entities.
  • Percentage values are given as decimals in the range 0–1 (e.g. 0.5 = 50%).

Additional fields (additionalAttributes)

Fund-specific additional fields (e.g. salarySymbol, employmentRatio, B-department fields, daysAtSea) are validated against the rules the fund advertises in entityTypeRules[].additionalAttributes from GET /fund-entities — see section 5.4 for the detailed shape and examples.

  • paymentEntry.additionalAttributes[].name must match a name advertised by the fund for the corresponding entityNo + entityType.
  • Fields marked required: true must be present on every matching paymentEntry.
  • value must be parseable as the declared type (string, number, boolean or date).
  • When allowedValues is set, value must be one of those values.
  • Fields not declared in the fund's rules should be ignored or raise a warning.

9. Error Handling

Use standard HTTP status codes. 200 OK should not be returned with an error payload.

ScenarioHTTP Statusresponse field
Fully accepted201ACCEPTED
Accepted with warnings201ACCEPTED_WITH_COMMENTS
Validation errors (business rules)422REJECTED
Malformed JSON or missing required fields400REJECTED
Reversal accepted200REVERSED
Reversal — submission not found404— (body optional)
Reversal — already booked or already reversed409REJECTED (detail in issues)
Missing or invalid Bearer token401— (no body required)
Valid token but insufficient scope403— (no body required)
Internal server error500

For 400 and 422 responses, always return a FundPaymentResponse body with the issues array populated. For 401 and 403, a body is optional; if included, keep it minimal to avoid leaking information about why authentication failed. For 5xx, a body is not required.


10. Implementation Checklist

This checklist may be used as a support tool during the upgrade. Note that it is not exhaustive and is provided only as a guide.

Discovery

  • GET /.well-known/skilagrein-configuration returns a valid CollectorConfiguration object
  • Endpoint is publicly accessible (no authentication required)
  • collectorId is the collector's identifier (SAL)
  • apiVersions contains at least one entry with validFrom set
  • endpoint points to a live Fund Payments API
  • openApiUrl points to the published OpenAPI spec
  • authentication block is complete and correct (see below)
  • validationEndpoint is included if the validation endpoint is implemented, otherwise omitted

Authentication

  • OAuth 2.0 authorization server is running and reachable at the published tokenUrl
  • client_credentials grant type is supported
  • The skilagrein scope (or an equivalent) is required and enforced
  • Token response includes access_token, token_type: "Bearer", and expires_in
  • Fund Payments API validates Bearer tokens on every request
  • 401 is returned for missing, expired, or invalid tokens
  • 403 is returned for valid tokens lacking the required scope
  • Credential issuance process is documented in credentialContact
  • HTTPS is enforced on all endpoints

Fund Payments API

  • POST /fund-payments accepts and processes submissions
  • DELETE /fund-payments/{transactionId} supports reversal and enforces the fund's rules for when reversal is allowed (200 REVERSED / 403 / 404 / 409)
  • GET /fund-entities returns the funds and entity types supported by the collector
  • Responses use FundPaymentResponse schema with correct response enum values
  • issues array is populated for ACCEPTED_WITH_COMMENTS and REJECTED responses
  • transactionId from submission is echoed back in every response
  • employeeTransactionRef is referenced in per-employee issues (not nationalId)
  • summaries are cross-validated against entry totals
  • Period validation is enforced (same reporting period, no year boundary crossing)
  • POST /fund-payments/validation implemented (if validationEndpoint is advertised)
  • entityTypeRules[].additionalAttributes advertises fund-specific additional fields with validation rules (name, type, and where applicable required, allowedValues)
  • paymentEntry.additionalAttributes are validated against the fund's rules (type, required, allowedValues)