Skip to main content

Introduction

The sandbox environment functions as a simulated version of the actual system, designed for testing and development purposes without interacting with real data. This environment employs mock payroll providers and payslip readers in order to allow developers to evaluate the API interface without compromising real data privacy.

Test Personas

The sandbox environment includes three pre-configured test personas with corresponding mock data in the backend. Use these personas to test different workflows throughout this guide.
These test personas currently return mock data for direct connections only. Support for other endpoints is coming soon.
Persona 1: Sarah Chen
{
  "forename": "sarah",
  "surname": "chen",
  "ni_number": "AB654321C",
  "date_of_birth": "1990-04-22T00:00:00.000Z",
  "post_codes": ["E1 6PG"],
  "payroll_period_months": 12,
  "ext_id": "ext-sarah-chen-001"
}
Persona 2: James O’Connor
{
  "forename": "james",
  "surname": "oconnor",
  "ni_number": "BC765432D",
  "date_of_birth": "1985-09-10T00:00:00.000Z",
  "post_codes": ["M13 9WL"],
  "payroll_period_months": 12,
  "ext_id": "ext-james-oconnor-002"
}
Persona 3: Amina Diallo
{
  "forename": "amina",
  "surname": "diallo",
  "ni_number": "CD876543E",
  "date_of_birth": "1978-12-03T00:00:00.000Z",
  "post_codes": ["B1 1TT"],
  "payroll_period_months": 12,
  "ext_id": "ext-amina-diallo-003"
}

Initial Setup

API Key & Callback URL

Before you begin testing, ensure you have the following:
  • An API key for authentication: please ask your Teal connection to supply you with an API key
  • A callback URL for receiving webhook notifications: generate your URL in webhook.site

Webhooks Setup /webhooks

Configure webhooks to receive notifications for events such as new payroll submissions.
curl --request POST \
  --url https://api.sandbox.goteal.co/webhooks \
  --header 'Content-Type: application/json' \
  --header 'X-API-KEY: <api-key>' \
  --data '{
  "events": [
    "user-payroll-submitted",
  ],
  "name": "user-payroll-submitted",
  "url": "https://webhooks.company.com"
}'
Optional fields: signing_secret (for HMAC verification), encryption_key (for AES/GCM payload encryption).
Response Example:
{
  "webhook_id": "95a0e70b-fe02-4f47-aef9-2efff279df71",
  "events": ["user-payroll-submitted"],
  "name": "user-payroll-submitted",
  "url": "https://webhooks.company.com",
  "created_at": "2019-05-17T00:00:00.000Z"
}

Configure Client Settings /configuration

Configure client-wide settings that affect sandbox behavior, such as payroll lookback periods and recurring checks.
curl --request PUT \
  --url https://api.sandbox.goteal.co/configuration \
  --header 'Content-Type: application/json' \
  --header 'X-API-KEY: <api-key>' \
  --data '{
  "payslip_upload_enabled": true,
  "payroll_period_months": 12,
  "recurring_checks_enabled": true,
  "recurring_check_frequency": "HOURLY",
  "recurring_check_end_date": "2027-01-10T00:00Z"
}'
Response Example:
{
  "payslip_upload_enabled": true,
  "payroll_period_months": 12,
  "client_logo": "",
  "recurring_checks_enabled": true,
  "recurring_check_frequency": "HOURLY",
  "recurring_check_end_date": "2027-01-10T00:00:00.000Z",
  "hmrc_enabled": true,
  "payroll_connections_enabled": true
}
In sandbox, you can test the HOURLY frequency (not available in production).

User Setup

Before starting any journey, complete the Initial Setup (Webhooks and Configuration) and User Setup steps below. These steps only need to be done once.

Create a User /users

Create a user using one of the test personas defined above. Include the ni_number to match the mock data for that persona. In this example, we’ll create Sarah Chen:
curl --request POST \
  --url https://api.sandbox.goteal.co/users \
  --header 'Content-Type: application/json' \
  --header 'X-API-KEY: <api-key>' \
  --data '{
  "forename": "sarah",
  "surname": "chen",
  "ni_number": "AB654321C",
  "date_of_birth": "1990-04-22T00:00:00.000Z",
  "post_codes": ["E1 6PG"],
  "payroll_period_months": 12
}'
Response Example:
{
  "user_id": "95a0e70b-fe02-4f47-aef9-2efff279df71",
  "forename": "Sarah",
  "surname": "Chen",
  "ni_number": "AB654321C",
  "date_of_birth": "1990-04-22T00:00:00.000Z",
  "post_codes": ["E1 6PG"],
  "payroll_period_months": 12,
  "created_at": "2019-05-17T00:00:00.000Z"
}

Generate a User Token /user-tokens

After creating a user, generate a token to authenticate subsequent actions for that user. The token ensures that actions such as account connection and payslip uploads are securely attributed to the correct user.
curl --request POST \
  --url https://api.sandbox.goteal.co/user-tokens \
  --header 'Content-Type: application/json' \
  --header 'X-API-KEY: <api-key>' \
  --data '{
  "user_id": "95a0e70b-fe02-4f47-aef9-2efff279df71"
}'
Response Example:
{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbGllbnRfbmFtZSI6ImNsaWVudC1uYW1lIiwidXNlcl9pZCI6Ijk1YTBlNzBiLWZlMDItNGY0Ny1hZWY5LTJlZmZmMjc5ZGY3MSIsImlzcyI6ImFwcC5zYW5kYm94LmdvdGVhbC5jbyIsImV4cCI6MTc3NTA0MjQzOH0.abc123"
}

Authorisation Terms Request /authorisations

Before connecting a payroll account, users must accept your terms and conditions. This request records the user’s consent:
curl --request POST \
  --url https://api.sandbox.goteal.co/authorisations \
  --header 'Content-Type: application/json' \
  --header 'X-API-KEY: <api-key>' \
  --data '{
  "user_id": "95a0e70b-fe02-4f47-aef9-2efff279df71",
  "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
}'
Optional fields: term_id (auto-resolves to latest active term if omitted), ip_address (captured from headers if omitted), fingerprint (device/browser fingerprint).
Response Example:
{
  "id": "95a0e70b-fe02-4f47-aef9-2efff279df71",
  "user_id": "95a0e70b-fe02-4f47-aef9-2efff279df71",
  "term_id": "550e8400-e29b-41d4-a716-446655440000",
  "accepted_text": "Teal will securely access and share information periodically from your payroll or payslip with your client, for the duration of your loan term.",
  "ip_address": "192.168.1.1",
  "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
  "status": "active",
  "is_valid": true,
  "accepted_at": "2019-05-17T00:00:00.000Z",
  "revoked_at": null
}

User Journeys

Direct Connection

This is the simplest way to get started with sandbox direct connections. The /accounts/direct-connections endpoint automatically checks all available providers and returns payroll data directly. Prerequisites: Complete Initial Setup (Webhooks, Configuration) and User Setup above.

Direct Connection /accounts/direct-connections

For sandbox direct connections, use this simplified endpoint that automatically checks all available direct connection providers and returns payroll data directly.
curl --request POST \
  --url https://api.sandbox.goteal.co/accounts/direct-connections \
  --header 'Content-Type: application/json' \
  --header 'X-API-KEY: <api-key>' \
  --data '{
  "user_id": "95a0e70b-fe02-4f47-aef9-2efff279df71"
}'
Response Example:
{
  "pagination": {
    "offset": 0,
    "limit": 12,
    "count": 12,
    "total_count": 12
  },
  "payroll_submissions": [
    {
      "id": "0187c66e-e7e5-811c-b006-2232f00f426a",
      "account_id": "95a0e70b-fe02-4f47-aef9-2efff279df71",
      "entry_id": "ddbb2453-1202-4ae8-9024-4365a223e548",
      "created_at": "2024-05-03T14:00:13.804Z",
      "authorisation_id": "5a299940-9753-4f36-8611-9aa02ba457f6",
      "identity_information": {
        "name": "Sarah Chen",
        "date_of_birth": "1990-04-22T00:00:00.000Z",
        "address": {
          "street": "45 Commercial Road",
          "city": "London",
          "post_code": "E1 6PG"
        },
        "phone": "+44 7700 900123",
        "NI_number": "AB654321C"
      },
      "employment_information": {
        "employer_name": "Acme Corp",
        "role": "Software Developer",
        "type": "Full_Time",
        "status": "Active",
        "start_date": "2018-06-01"
      },
      "income_information": {
        "pay_date": "2024-02-15",
        "pay_interval_start": "2024-02-01",
        "pay_interval_end": "2024-02-15",
        "pay_frequency": "Weekly",
        "earnings": {
          "gross_pay": "2000.00",
          "net_pay": "1500.00",
          "base_salary": "1900.00",
          "bonus": "100.00"
        },
        "deductions": {
          "income_tax": "300.00",
          "employee_ni": "150.00",
          "employee_pension": "50.00",
          "total_deductions": "500.00"
        }
      }
    }
  ]
}
This endpoint:
  • Takes just a user_id (no provider selection needed)
  • Automatically checks all available direct connection providers
  • Creates accounts automatically for any provider with data
  • Returns payroll data directly (no separate /payroll/entries/connections call needed)

User Connection

This journey describes connecting to a specific payroll provider. Use this when you need to connect to a particular provider or when the simplified direct connection doesn’t meet your needs. Prerequisites: Complete Initial Setup (Webhooks, Configuration) and User Setup above.

Scenario-Based Testing

For certain providers, you can test realistic flows by sending specific usernames that match predefined scenarios. These scenarios simulate real-world conditions such as MFA challenges, incorrect credentials, and successful logins. The user you create does not need to match the test user names listed below. The password can be set to any value. If you send a user_name that does not match a scenario username, the sandbox will return fake data as normal.
If you use a scenario username but do not supply the corresponding payroll date range, no payroll data will be returned.
ProviderEmployerTest UserScenarioUsernamePayroll Date RangeMFAMFA TypeMFA Code
BrightPayCloudAim SmarterSarah Mitchellhappy_no_mfahappy_no_mfa2025-08-01 to 2025-08-31No
CGIDepartment for Environment, Food and Rural AffairsDavid Patelhappy_mfahappy_mfa2025-10-01 to 2025-10-31YesPasswordWithPositions — Pin Code (positions 2, 4, 6)123
AdpMyAdpBrightwellEmma Richardsonhappy_no_mfahappy_no_mfa2025-05-01 to 2025-05-31No
HeroMarcus Thompsonhappy_mfahappy_mfa2025-05-01 to 2025-05-31YesCode — Authenticator App140295
The available scenario usernames are: happy_mfa, happy_no_mfa, wrong_mfa, wrong_username, and wrong_password. When sending date filters via the API, ensure they are in ISO 8601 format (e.g. 2025-08-01T00:00:00Z). These scenarios can also be used on the sandbox webapp. When testing via the webapp, ensure the user’s payroll_period_months lookback is set large enough to cover the scenario’s payroll date range.

Connect a Payroll Account /accounts

This simulates the user connecting their payroll account for automatic data retrieval.
curl --request POST \
  --url https://api.sandbox.goteal.co/accounts \
  --header 'Content-Type: application/json' \
  --header 'X-API-KEY: <api-key>' \
  --data '{
  "user_id": "95a0e70b-fe02-4f47-aef9-2efff279df71",
  "payroll_provider": "quickbooks",
  "user_name": "john.smith@company.com",
  "password": "example-password"
}'
Optional fields: user_name, password, extra_login_params (e.g. company_name for Dayforce).
Response Example:
{
  "account_id": "95a0e70b-fe02-4f47-aef9-2efff279df71",
  "payroll_provider": "quickbooks",
  "user_name": "john.smith@company.com",
  "created_at": "2019-05-17T00:00:00.000Z",
  "status": "Authorised"
}
Use the optional extra_login_params object for provider-specific login fields (omit it when it isn’t required). Dayforce connections require setting extra_login_params.company_name. As with production accounts, when connecting we will retrieve payroll immediately. For sandbox, when not using scenarios, the number of payroll submissions generated matches the payroll_period_months configuration. If this is set on both the client and user configuration, the user level config takes precedence. For example, if it is set to 7 months, we’ll return 8 payroll submissions (including the current month). If it is not set we’ll take a default of 36 months. To simulate a gap in the payroll, the Shape provider will leave a 3 month gap from the time of the request.

Retrieve Submitted Payroll Information /payroll/entries/connections

Retrieve latest payroll for a connected account. For sandbox, we’ll always return new payroll where the pay_date is set to the current day. For Shape provider we will still maintain a 3 month gap.
curl --location --request POST 'https://api.sandbox.goteal.co/payroll/entries/connections' \
--header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbGllbnRfbmFtZSI6InN5c19jbGllbnQxIiwidXNlcl9pZCI6IjU3YWRiMzg0LWE2ZGEtNGE1Ny05NThhLTU1ZDk5MzdjZWE0MyIsImlzcyI6ImFwcC5zYW5kYm94LmdvdGVhbC5jbyIsImV4cCI6MTcxNTM0NTI5MH0.IxgmSN4XAn7j2jbQu0npwLXQjEQVqcbjmIBO2T-NCAc' \
--header 'Content-Type: application/json' \
--data-raw '{
    "account_id" : 552300dd-1e91-48d4-958b-41736c2c0167
}'
Response Example:
{
  "account_id": "552300dd-1e91-48d4-958b-41736c2c0167",
  "entry_id": "bdc420fe-5e48-4802-a753-659979a2a346",
  "payroll_submissions": [
    {
      "id": "0187c66e-e7e5-811c-b006-2232f00f426a",
      "account_id": "552300dd-1e91-48d4-958b-41736c2c0167",
      "entry_id": "bdc420fe-5e48-4802-a753-659979a2a346",
      "created_at": "2024-05-03T14:00:13.804Z",
      "identity_information": {
        "name": "Sarah Chen",
        "date_of_birth": "1990-04-22T00:00:00.000Z",
        "address": {
          "street": "45 Commercial Road",
          "county": "Greater London",
          "city": "London",
          "post_code": "E1 6PG",
          "country": "United Kingdom"
        },
        "email": "sarah.chen@example.com",
        "phone": "+447700900123",
        "NI_number": "AB654321C"
      },
      "employment_information": {
        "employer_name": "Acme Corp",
        "role": "Software Developer",
        "type": "Full-Time",
        "status": "Active",
        "start_date": "2018-06-01",
        "leave_date": null
      },
      "income_information": {
        "pay_date": "2024-02-15",
        "pay_interval_start": "2024-02-01T00:00:00.000Z",
        "pay_interval_end": "2024-02-15T00:00:00.000Z",
        "pay_frequency": "weekly",
        "earnings": {
          "gross_pay": 2000,
          "net_pay": 1500,
          "base_salary": 1900,
          "bonus": 100
        },
        "deductions": {
          "income_tax": 300,
          "employee_ni": 150,
          "employee_pension": 50,
          "total_deductions": 500
        }
      }
    },
    {
      "id": "0187c66e-e7e5-811c-b006-2232f00f426a",
      "account_id": "552300dd-1e91-48d4-958b-41736c2c0167",
      "entry_id": "bdc420fe-5e48-4802-a753-659979a2a346",
      "created_at": "2024-05-03T14:00:13.804Z",
      "identity_information": {
        "name": "Sarah Chen",
        "date_of_birth": "1990-04-22T00:00:00.000Z",
        "address": {
          "street": "45 Commercial Road",
          "county": "Greater London",
          "city": "London",
          "post_code": "E1 6PG",
          "country": "United Kingdom"
        },
        "email": "sarah.chen@example.com",
        "phone": "+447700900123",
        "NI_number": "AB654321C"
      },
      "employment_information": {
        "employer_name": "Acme Corp",
        "role": "Software Developer",
        "type": "Full-Time",
        "status": "Active",
        "start_date": "2018-06-01",
        "leave_date": null
      },
      "income_information": {
        "pay_date": "2024-02-15",
        "pay_interval_start": "2024-02-01T00:00:00.000Z",
        "pay_interval_end": "2024-02-15T00:00:00.000Z",
        "pay_frequency": "weekly",
        "earnings": {
          "gross_pay": 2000,
          "net_pay": 1500,
          "base_salary": 1900,
          "bonus": 100
        },
        "deductions": {
          "income_tax": 300,
          "employee_ni": 150,
          "employee_pension": 50,
          "total_deductions": 500
        }
      }
    },
    {
      "id": "0187c66e-e7e5-811c-b006-2232f00f426a",
      "account_id": "552300dd-1e91-48d4-958b-41736c2c0167",
      "entry_id": "bdc420fe-5e48-4802-a753-659979a2a346",
      "created_at": "2024-05-03T14:00:13.804Z",
      "identity_information": {
        "name": "Sarah Chen",
        "date_of_birth": "1990-04-22T00:00:00.000Z",
        "address": {
          "street": "45 Commercial Road",
          "county": "Greater London",
          "city": "London",
          "post_code": "E1 6PG",
          "country": "United Kingdom"
        },
        "email": "sarah.chen@example.com",
        "phone": "+447700900123",
        "NI_number": "AB654321C"
      },
      "employment_information": {
        "employer_name": "Acme Corp",
        "role": "Software Developer",
        "type": "Full-Time",
        "status": "Active",
        "start_date": "2018-06-01",
        "leave_date": null
      },
      "income_information": {
        "pay_date": "2024-02-15",
        "pay_interval_start": "2024-02-01T00:00:00.000Z",
        "pay_interval_end": "2024-02-15T00:00:00.000Z",
        "pay_frequency": "weekly",
        "earnings": {
          "gross_pay": 2000,
          "net_pay": 1500,
          "base_salary": 1900,
          "bonus": 100
        },
        "deductions": {
          "income_tax": 300,
          "employee_ni": 150,
          "employee_pension": 50,
          "total_deductions": 500
        }
      }
    }
  ]
}

Payslip Upload

This journey outlines the process for extracting payroll information by uploading a payslip. Prerequisites: Complete Initial Setup (Webhooks, Configuration) and User Setup above.

Upload Payslip /payroll/entries/payslips

This request simulates the user uploading a payslip to extract payroll information. Extracting payslip information can be done using the endpoint below but will usually be done by the user through the WebApp. This example uses James O’Connor:
curl --location --request POST 'https://api.sandbox.goteal.co/payroll/entries/payslips' \
--header 'Authorization: Bearer <bearer-token>' \
--form 'file=@"<path-to-file>"'
Response Example:
{
  "account_id": "f2a8e9c1-d3b4-4f5a-9e8d-7c6b5a4f3e2d",
  "entry_id": "f5e0a310-d4d0-4db0-b5c3-895a81e26e03",
  "payroll_submissions": [
    {
      "id": "0187c66e-e7e5-811c-b006-2232f00f426a",
      "account_id": null,
      "entry_id": "f5e0a310-d4d0-4db0-b5c3-895a81e26e03",
      "created_at": "2024-05-03T12:48:54.886Z",
      "identity_information": {
        "name": "James O'Connor",
        "date_of_birth": "1985-09-10T00:00:00.000Z",
        "address": {
          "street": "12 Oxford Road",
          "county": "Greater Manchester",
          "city": "Manchester",
          "post_code": "M13 9WL",
          "country": "United Kingdom"
        },
        "email": "james.oconnor@example.com",
        "phone": "+447700900456",
        "NI_number": "BC765432D"
      },
      "employment_information": {
        "employer_name": "Acme Corp",
        "role": "Software Developer",
        "type": "Full-Time",
        "status": "Active",
        "start_date": "2018-06-01",
        "leave_date": null
      },
      "income_information": {
        "pay_date": "2024-02-15",
        "pay_interval_start": "2024-02-01T00:00:00.000Z",
        "pay_interval_end": "2024-02-15T00:00:00.000Z",
        "pay_frequency": "weekly",
        "earnings": {
          "gross_pay": 2000,
          "net_pay": 1500,
          "base_salary": 1900,
          "bonus": 100
        },
        "deductions": {
          "income_tax": 300,
          "employee_ni": 150,
          "employee_pension": 50,
          "total_deductions": 500
        }
      }
    }
  ]
}

General Data Retrieval

Retrieve Payroll Data /payroll

Retrieve the payroll data submitted through a connected account or document upload. This example retrieves payroll for Amina Diallo:
curl --request GET \
  --url https://api.sandbox.goteal.co/payroll/{user_id} \
  --header 'X-API-KEY: <api-key>'
Response Example:
{
  "pagination": {
    "count": 14,
    "limit": 25,
    "offset": 75,
    "total_count": 89
  },
  "payroll_submissions": [
    {
      "id": "95a0e70b-fe02-4f47-aef9-2efff279df71",
      "account_id": "0988b4bd-42dd-4556-95ae-27a906c066ec",
      "entry_id": "0d2bcb9b-a7ec-463f-bd7e-aacaa1a190a4",
      "created_at": "2019-05-17T00:00:00.000Z",
      "identity_information": {
        "NI_number": "CD876543E",
        "address": {
          "city": "Birmingham",
          "country": "United Kingdom",
          "county": "West Midlands",
          "post_code": "B1 1TT",
          "street": "78 Victoria Square"
        },
        "date_of_birth": "1978-12-03T00:00:00.000Z",
        "email": "amina.diallo@example.com",
        "name": "Amina Diallo",
        "phone": 447700900789
      },
      "employment_information": {
        "employer_name": "Acme Ltd",
        "role": "Software Engineer",
        "type": "Full-time",
        "status": "active",
        "start_date": "2019-05-17T00:00:00.000Z",
        "leave_date": "2019-05-17T00:00:00.000Z"
      },
      "income_information": {
        "deductions": {
          "employee_ni": 200,
          "employee_pension": 300,
          "income_tax": 500,
          "total_deductions": 1000
        },
        "earnings": {
          "base_salary": 3000,
          "bonus": 500,
          "gross_pay": 3500,
          "net_pay": 2500
        },
        "pay_date": "2019-05-17T00:00:00.000Z",
        "pay_frequency": "Monthly",
        "pay_interval_end": "2019-05-17T00:00:00.000Z",
        "pay_interval_start": "2019-05-17T00:00:00.000Z"
      },
      "document_external_id": null,
      "document_filename": null,
      "document_filename": "file1-payslip.pdf",
      "fraud_risk": "Low"
    }
  ]
}

WebApp Usage

Teal’s webapp can be tested by accessing https://app.sandbox.goteal.co/ in the browser. Use the generated bearer token in order to identify the user https://app.sandbox.goteal.co/?jwt=<bearer-token>. The webapp uses the same API mechanism as mentioned above, therefore the data will be retrieved in the same way. You can test the webapp with any of the three personas defined at the top of this guide (Sarah Chen, James O’Connor, or Amina Diallo). Connect a Payroll: payroll connection Submit a Payslip: payslip submission

Cleanup

Deleting a User /users

After testing, clean up by deleting the test user and their associated data. Use the user_id returned when you created the user (from any of the test personas).
curl --request DELETE \
  --url https://api.sandbox.goteal.co/users/{user_id} \
  --header 'X-API-KEY: <api-key>'
Response Example:
{
  "message": "User deleted successfully."
}

OAuth2 Test Credentials

Some of our providers use an authorisation flow known as OAuth2. This allows the end user to be redirected to their payroll provider login screen, where they can grant authorisation to share their data. Below is an example login page: OAuth2 Login The user will be redirected back to teal after authorising. To test the providers that have the OAuth2 flow available, you can use the following credentials.
providerusernamepasswordExtra login params
freeagentarun@goteal.comPxD9Tt3hBV5j39