v1.0.0
OpenAPI 3.1.1

PayrollKit Partner API

Use the PayrollKit API to register Employers, supply payroll data, calculate and approve Pay Runs, follow HMRC Filings and retrieve payroll outputs. Send requests to https://api.payrollkit.co/v1.

This is the intended API contract. The hosted service and installable packages are not yet available; the Developer Preview will identify its implemented operations and tested package versions.

New to PayrollKit? Read How to integrate PayrollKit and What PayrollKit supports before using this reference.

Authentication and permissions

Request an access token

PayrollKit uses OAuth 2.0 client credentials. Request tokens from your server and never expose credentials or tokens in browser code, URLs, analytics or logs. Contact partners@payrollkit.io for a client ID and secret.

Send the client ID and secret using HTTP Basic authentication. Send the token request as application/x-www-form-urlencoded, not JSON:

Form field Required Value
grant_type Yes client_credentials
scope No Space-separated permissions. Omit it to request the client's default permissions.
curl --request POST \
  --url https://auth.payrollkit.co/oauth/token \
  --user 'YOUR_CLIENT_ID:YOUR_CLIENT_SECRET' \
  --header 'Accept: application/json' \
  --header 'Content-Type: application/x-www-form-urlencoded' \
  --data-urlencode 'grant_type=client_credentials'

The response uses standard OAuth fields. expires_in is expressed in seconds, and scope lists the granted permissions separated by spaces:

{
  "access_token": "YOUR_SERVER_ACCESS_TOKEN",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "employers:read employers:write payroll:read payroll:write outputs:read outputs:write embedded:write webhooks:write"
}

Use an access token

You can cache the access token and reuse it until 60 seconds before it expires. After that, request a new token before making further API calls. Send it in the Authorization header as Bearer YOUR_SERVER_ACCESS_TOKEN.

The access token identifies your Partner account, so requests do not include a Partner ID.

Operations that record who took an action also require the user headers documented for that operation. A 401 means the token is missing, expired or invalid; a 403 means a valid token lacks the required permission.

Request permissions

Omit scope to request the client's default permissions; permissions requiring an explicit request are excluded. To request a subset, provide a space-separated value such as --data-urlencode 'scope=employers:read employers:write'. Requesting a permission not configured for the client returns invalid_scope.

payroll:approve requires a separate agreement and an explicit request. Approval in the PayrollKit component does not require this permission on the Partner token.

Data formats

Requests and responses use JSON over HTTPS, with camel-case property names.

Value Representation
Money Integer pence with an explicit GBP currency
Non-money decimal values, such as hours and multipliers Decimal strings
Dates ISO 8601 calendar dates (YYYY-MM-DD)
Timestamps ISO 8601 timestamps with an offset

Safe writes

Network failures can leave you unsure whether a write succeeded. Idempotency keys let you retry without applying the same change twice.

Choose an idempotency key

Every write requires an Idempotency-Key. Create one before the first request and reuse it only when retrying that exact request. Use a new key for every intended change.

Keys must contain 16 to 200 letters, numbers, full stops, underscores, colons or hyphens. We recommend using a UUID or another stable identifier for the intended change:

curl --request POST \
  --url https://api.payrollkit.co/v1/employers \
  --header 'Authorization: Bearer YOUR_SERVER_ACCESS_TOKEN' \
  --header 'Content-Type: application/json' \
  --header 'Idempotency-Key: employer:company_4821:create:v1' \
  --data '{ "partnerEmployerId": "company_4821", "legalName": "Lantern House Hospitality Ltd" }'

Retry an uncertain write

If a request times out or its outcome remains uncertain after a 5xx, retry the unchanged request with the same key. PayrollKit stores idempotency results for 30 days. After that, PayrollKit treats a request using the same key as new, so do not retry automatically once the key expires.

Key reuse Response
Identical request after completion The original response, with Idempotency-Replayed: true.
Identical request while still running 409 idempotency_in_progress, with Retry-After.
Different request 422 idempotency_key_reused.

Retry comparison

PayrollKit compares the Partner, method, path, sorted query parameters, content type and parsed JSON body, together with If-Match, X-Partner-User-Id and X-Partner-User-Name when those headers are present. JSON whitespace and object-key order are ignored; array order matters, and duplicate keys are rejected. The access-token value, X-Request-Id and Idempotency-Key are not part of this comparison.

No-change updates

An update that makes no changes still runs validation, lifecycle and If-Match checks, then returns the current resource and ETag. PayrollKit stores the result for retries but does not create a revision, change the update timestamp or send a resource.changed notification.

Prevent overwriting newer information

Editable resources return an ETag. Send that value in If-Match when editing the resource. The idempotency key stops one change from being applied twice; If-Match stops it from overwriting a newer change.

If PayrollKit reports a revision conflict, retrieve the current resource and decide whether the change still applies. Send that decision as a new request with a new idempotency key.

Long-running operations

Calculations, Filings, Payment Instruction Files and requested exports may complete asynchronously. While one of these resources still requires polling, its retrieval response includes Retry-After. Wait for that interval before checking again. Stop automatic polling after 15 minutes and show that the work is delayed. A later check or webhook can still report completion.

Errors and retries

Errors use the standard Problem Details format (application/problem+json). The response includes a stable code, a request ID for support and an issues array that explains affected records or fields and what to do next.

{
  "type": "https://docs.payrollkit.co/problems/revision-conflict",
  "title": "Resource changed",
  "status": 409,
  "code": "revision_conflict",
  "detail": "The supplied revision is no longer current.",
  "requestId": "6f5be2a1-4e1e-4b97-aef8-748db57491e0",
  "issues": [
    {
      "code": "stale_revision",
      "message": "The supplied If-Match revision is stale.",
      "fieldPath": "/headers/If-Match",
      "currentRevision": "rev_4f69db53",
      "blockedStep": "request",
      "nextAction": "Retrieve the current resource before applying the change."
    }
  ]
}
Status What to do
400 Correct the invalid request field named in fieldPath.
401 Get a valid access token.
403 Use a client with the required permission.
404 Check the PayrollKit ID stored for your record.
409 Follow nextAction, or wait when another request is still running.
422 Follow nextAction to correct the problem described in issues.
429 Wait for Retry-After.
503 Retry an unchanged write with the same idempotency key.

Rate limits

Each Partner can make 600 requests in a rolling 60-second window. A 429 response includes RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset and Retry-After headers.

List operations return an items array and a nextCursor. Request the first page without a cursor. Use limit to choose between 1 and 100 records; the default is 50.

Send nextCursor back unchanged as cursor, keeping the endpoint, filters and other query parameters the same. Continue until nextCursor is null. Do not inspect, alter or create cursor values.

The first request fixes the records and ordering used for that sequence. A cursor works only for the Partner, endpoint and filters that produced it and expires after 24 hours. If PayrollKit returns 400 invalid_cursor, start again without a cursor. Use webhooks for ongoing changes instead of repeatedly listing every record.

Enum values

Response enum lists are exhaustive. Request fields accept only documented values; an unknown request value returns 400 request_invalid.

If a response contains an unknown enum value, do not replace it or act on it. Keep the response and request ID, mark the affected work as needing attention and contact PayrollKit support. PayrollKit may add accepted request values.

Compatibility

PayrollKit may add optional response fields to v1. Your integration must ignore fields it does not recognise, so these additions do not require an immediate update.

PayrollKit will introduce a new URL version before making a breaking change. This includes removing or renaming a field or enum value, changing a field's meaning or type, making an optional request field required, or adding a response enum value.

Production API