Skip to main content
POST
/
entries
/
payslips
# Step 1: request presigned upload URLs
curl --request POST \
  --url https://api.sandbox.goteal.co/entries/payslips/presign \
  --header 'Authorization: Bearer <token>' \
  --header 'Content-Type: application/json' \
  --data '{
    "files": [
      {"file_name": "payslip1.pdf", "external_id": "ext-jan-2024"},
      {"file_name": "payslip2.pdf"}
    ]
  }'

# Step 2: upload PDFs with the provided presigned URLs
curl --request PUT \
  --url "<presigned_url_from_step_1>" \
  --header 'Content-Type: application/octet-stream' \
  --data-binary '@./fixtures/payslip1.pdf'

# Step 3: submit uploaded payslips for processing
curl --request POST \
  --url https://api.sandbox.goteal.co/entries/payslips \
  --header 'Authorization: Bearer <token>' \
  --header 'Content-Type: application/json' \
  --data '{
    "files": [
      {"path": "client/acct123/users/user456/payslip1.pdf", "external_document_id": "ext-jan-2024"},
      {"path": "client/acct123/users/user456/payslip2.pdf"}
    ]
  }'
{
  "account_id": "95a0e70b-fe02-4f47-aef9-2efff279df71",
  "entry_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
  "payroll_submissions": [
    {
      "id": "95a0e70b-fe02-4f47-aef9-2efff279df71",
      "account_id": "674744df-9626-47ef-ae2b-4a491be136b5",
      "entry_id": "be770ba4-1362-46cd-8c1c-2330ce3a8b69",
      "created_at": "2019-05-17T00:00:00.000Z",
      "document_external_id": "payslip123456",
      "document_filename": "file1-payslip.pdf",
      "identity_information": {
        "name": "John Smith",
        "date_of_birth": "2019-05-17T00:00:00.000Z",
        "address": {
          "street": "123 Main Street",
          "county": "Greater London",
          "city": "London",
          "post_code": "SW1A 1AA",
          "country": "United Kingdom"
        },
        "email": "john.smith@company.com",
        "phone": 447123456789,
        "NI_number": "AB123456C"
      },
      "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": {
        "pay_date": "2023-05-27T00:00:00.000Z",
        "pay_interval_start": "2023-05-01T00:00:00.000Z",
        "pay_interval_end": "2023-05-31T00:00:00.000Z",
        "pay_frequency": "Monthly",
        "earnings": {
          "gross_pay": 3500,
          "net_pay": 2500,
          "base_salary": 3000,
          "bonus": 500
        },
        "deductions": {
          "income_tax": 500,
          "employee_ni": 200,
          "employee_pension": 300,
          "total_deductions": 1000
        }
      }
    },
    "fraud_risk": "Low"
    ],
  "payslip_errors" : [{
    "error" : "File is not a payslip",
    "file_name" : "Payslip3.pdf"
  }]
}

End-to-end upload flow

Follow these steps to upload payslips programmatically:
  1. Request upload slots – call POST /entries/payslips/presign with the list of filenames (and optional external identifiers) you intend to upload. The response returns presigned_url and path fields for each file.
  2. Upload PDFs – for every returned presigned_url, perform an HTTP PUT with Content-Type: application/octet-stream, streaming the PDF bytes. Files must be searchable, machine-readable PDFs (scanned images are not supported).
  3. Submit metadata – call POST /entries/payslips (this endpoint) with a JSON payload that includes the path for each uploaded file and an optional external_document_id. Teal uses this metadata to process the uploaded documents. Any files that fail validation are listed in payslip_errors in the response.
# Step 1: request presigned upload URLs
curl --request POST \
  --url https://api.sandbox.goteal.co/entries/payslips/presign \
  --header 'Authorization: Bearer <token>' \
  --header 'Content-Type: application/json' \
  --data '{
    "files": [
      {"file_name": "payslip1.pdf", "external_id": "ext-jan-2024"},
      {"file_name": "payslip2.pdf"}
    ]
  }'

# Step 2: upload PDFs with the provided presigned URLs
curl --request PUT \
  --url "<presigned_url_from_step_1>" \
  --header 'Content-Type: application/octet-stream' \
  --data-binary '@./fixtures/payslip1.pdf'

# Step 3: submit uploaded payslips for processing
curl --request POST \
  --url https://api.sandbox.goteal.co/entries/payslips \
  --header 'Authorization: Bearer <token>' \
  --header 'Content-Type: application/json' \
  --data '{
    "files": [
      {"path": "client/acct123/users/user456/payslip1.pdf", "external_document_id": "ext-jan-2024"},
      {"path": "client/acct123/users/user456/payslip2.pdf"}
    ]
  }'
{
  "account_id": "95a0e70b-fe02-4f47-aef9-2efff279df71",
  "entry_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
  "payroll_submissions": [
    {
      "id": "95a0e70b-fe02-4f47-aef9-2efff279df71",
      "account_id": "674744df-9626-47ef-ae2b-4a491be136b5",
      "entry_id": "be770ba4-1362-46cd-8c1c-2330ce3a8b69",
      "created_at": "2019-05-17T00:00:00.000Z",
      "document_external_id": "payslip123456",
      "document_filename": "file1-payslip.pdf",
      "identity_information": {
        "name": "John Smith",
        "date_of_birth": "2019-05-17T00:00:00.000Z",
        "address": {
          "street": "123 Main Street",
          "county": "Greater London",
          "city": "London",
          "post_code": "SW1A 1AA",
          "country": "United Kingdom"
        },
        "email": "john.smith@company.com",
        "phone": 447123456789,
        "NI_number": "AB123456C"
      },
      "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": {
        "pay_date": "2023-05-27T00:00:00.000Z",
        "pay_interval_start": "2023-05-01T00:00:00.000Z",
        "pay_interval_end": "2023-05-31T00:00:00.000Z",
        "pay_frequency": "Monthly",
        "earnings": {
          "gross_pay": 3500,
          "net_pay": 2500,
          "base_salary": 3000,
          "bonus": 500
        },
        "deductions": {
          "income_tax": 500,
          "employee_ni": 200,
          "employee_pension": 300,
          "total_deductions": 1000
        }
      }
    },
    "fraud_risk": "Low"
    ],
  "payslip_errors" : [{
    "error" : "File is not a payslip",
    "file_name" : "Payslip3.pdf"
  }]
}

Authorizations

Authorization
string
header
required

Bearer token for authentication. The token should be the one returned by the /user-tokens endpoint.

Body

application/json
files
object[]
required
Minimum length: 1

Response

Created

account_id
string<uuid>
required

The id of the account

Example:

"95a0e70b-fe02-4f47-aef9-2efff279df71"

entry_id
string<uuid>
required

The id of the entry

payroll_submissions
object[]
required
payslip_errors
object[]

List of errors for payslips that passed validation but failed to upload

I