Skip to main content

Claim additional payments for teaching - HMRC sandbox employment data

These tasks use the Create Test User and Integration Framework Test Support APIs documented in hmrc-api-docs. They create National Insurance individuals and DFE employment responses; no Self Assessment enrolment or data is created.

Configuration

Set the existing employment client variables in your local environment:

HMRC_EMPLOYMENTS_BASE_URL=https://test-api.service.hmrc.gov.uk
HMRC_EMPLOYMENTS_CLIENT_ID=...
HMRC_EMPLOYMENTS_CLIENT_SECRET=...
# HMRC_EMPLOYMENTS_TOTP_SECRET=... # if required by your application

Your sandbox application needs access to Create Test User, Integration Framework Test Support, Individuals Matching and Individuals Employments (v2). The tasks reuse the employment client's authentication, including optional TOTP. They reject other base URLs. HMRC_EMPLOYMENTS_ENABLED does not gate these explicit tasks; enable it separately when testing the wizard.

The token request uses scope=assigned. When the OAuth bypass secret is set, the client prefixes the client secret with an eight-digit SHA512 TOTP (30-second interval), matching the employment API's authentication flow. The variable is HMRC_EMPLOYMENTS_TOTP_SECRET (TOTP, not TOPT). A misspelling leaves the OTP disabled and can cause invalid_client. After changing .env, reload it; for shell variables to reach subprocesses, use set -a; source .env; set +a.

Create a teacher and one employment

To first diagnose access to the Create Test User API without creating a user:

bin/rails hmrc:sandbox:services

This calls GET /create-test-user/services with the same authentication and v1 Accept header as user creation, logs the endpoint/status and prints the returned services as JSON. A failure exits with the HMRC error as usual.

bin/rails hmrc:sandbox:create_user \
  EMPLOYER_NAME='Example Nursery' PAYE_REFERENCE=123/AB456 \
  START_DATE=2025-09-01

Use the nursery's exact name: the current EYTFI employment check compares employer names. END_DATE=YYYY-MM-DD is optional; omit it for an ongoing employment. The generated name, date of birth, NINO and sign-in details are printed and saved in tmp/hmrc-sandbox/<NINO>.json. The user is saved before employment is posted, so a failed seed does not lose the identity. Files contain sandbox credentials and are created with permissions 0600; the directory is ignored by Git.

To create only the individual, omit the employment parameters:

bin/rails hmrc:sandbox:create_user

HMRC generates identity details. Optionally supply NINO=AA123456A to request a particular test NINO (for example to align with another test system). Use the returned identity when calling Individuals Matching.

Seed an existing user's employments

bin/rails hmrc:sandbox:create_employments NINO=AA123456A \
  EMPLOYER_NAME='Example Nursery' PAYE_REFERENCE=123/AB456 \
  START_DATE=2025-09-01

To include an employer address:

bin/rails hmrc:sandbox:create_employments NINO=AA123456A \
  EMPLOYER_NAME='Example Nursery' PAYE_REFERENCE=123/AB456 \
  START_DATE=2025-09-01 \
  EMPLOYER_ADDRESS_LINE_1='10 Example Road' \
  EMPLOYER_ADDRESS_LINE_2='Leeds' EMPLOYER_POSTCODE='LS1 1AA'

EMPLOYER_ADDRESS_LINE_1 through EMPLOYER_ADDRESS_LINE_5 and EMPLOYER_POSTCODE are optional, including when seeding employment through create_user. They populate employer.address.line1 through line5 and employer.address.postcode. Blank fields are omitted; supplying only a postcode is supported. With no address fields, the address object is omitted.

For multiple employments, pass EMPLOYMENTS_FILE=/path/to/employments.json to either create_user or create_employments. This replaces the single-employment parameters and sends the supplied IF payload. For example:

{
  "employments": [
    {
      "employerRef": "123/OLD",
      "employer": {"name": "Previous Nursery", "districtNumber": "123", "schemeRef": "OLD"},
      "employment": {"startDate": "2024-09-01", "endDate": "2025-08-31"}
    },
    {
      "employerRef": "123/NEW",
      "employer": {
        "name": "Example Nursery", "districtNumber": "123", "schemeRef": "NEW",
        "address": {"line1": "10 Example Road", "line2": "Leeds", "postcode": "LS1 1AA"}
      },
      "employment": {"startDate": "2025-09-01"}
    }
  ]
}

Use {"employments": []} for an empty history. JSON mode checks the top-level shape; HMRC validates the IF fields. No payment or Self Assessment data is needed. All employment seeds use useCase=DFE. Optional FROM_DATE and TO_DATE set the stub's startDate and endDate query parameters. These describe the requested history window, independently of each employment's start/end dates. When setting a window, use the same dates when fetching the history.

Fetch employment history

To check whether supplied identity details match an individual in HMRC's sandbox:

bin/rails hmrc:sandbox:match_user FIRST_NAME=Seymour LAST_NAME=Skinner \
  NINO=AB123456C DATE_OF_BIRTH=1970-01-01

All four arguments are required. Quote names containing spaces. This uses POST /individuals/matching/ with the v2 Accept header, without needing a saved identity or fetching employment data. Output contains matched: true and HMRC's response (including the match link), or matched: false for 404 MATCHING_FAILED. A non-match means the supplied details did not match; it does not prove the NINO does not exist. Other API errors fail the task as usual.

bin/rails hmrc:sandbox:fetch_employments NINO=AA123456A FROM_DATE=2025-09-01
# Optionally add TO_DATE=2026-09-30 and/or PAYE_REFERENCE=123/AB456

fetch_employments calls the real matching and employment endpoints through Hmrc::Employments::Client, using the locally saved identity.

API limitations

Sandbox requests log their HTTP method, full URL (including query parameters), and response status to stderr. This includes OAuth, matching and employment requests, so a FORBIDDEN response can be traced to its endpoint. Headers and request/response bodies are not logged by the client. URLs can contain sandbox NINOs and match IDs.

If both user creation and the services diagnostic return 403 FORBIDDEN with This endpoint is not available, check the application's IP allow list: a changed public IP caused this response even though OAuth token issuance succeeded.

The supplied APIs have no endpoints for listing, retrieving, updating or deleting test users, nor for updating or deleting seeded employment data. The IF docs say each endpoint can be populated only once per user. Supply all employments in one request. To change a scenario, create a new user and seed the revised history. After an ambiguous network failure, try fetching the history before retrying a seed: the first request may have succeeded.

Saved identity files allow fetch_employments to match users created by these tasks. Removing a local file does not delete the HMRC user. HMRC says users unused for 90 days are deleted; local files can outlive them. Preserve the files if you want to fetch their employment history after clearing tmp.