> ## Documentation Index
> Fetch the complete documentation index at: https://docs.goteal.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Get ONS expenditure

> Estimates cost of living and housing from the user's payroll periods,
date of birth, and postcode using the Teal V3.0 ONS Family Spending
model (table A6).

Net pay is split across calendar months by overlapping days. Age is
taken as of the last day of each month from that period's date of
birth (else the user's) and adjusts cost of living only.
Region (from each period's postcode prefix, else the user's first
postcode that has an ONS region) adjusts housing only.

Months are skipped, not returned, when their net pay is zero or
negative (refunds, reversals) or when no postcode for the month has
an ONS region (for example GY, JE, IM or an unknown postcode).

`404` lists each missing or invalid input as its own string in
`errors`:
- `User [<user_id>] not found`
- `[date_of_birth] param is missing on the user and its payslips`
- `[post_codes] param is missing on the user and its payslips`
- `[date_of_birth] param <date> is after the payslip month`
- `[date_of_birth] param <date> gives an age over 150, the ONS model limit`
- `Postcode <postcode> has no ONS region; supported: England, Wales, Scotland, Northern Ireland`
  (only when no month has a postcode with a region)
- `payroll periods are missing`
- `payroll periods have no positive net pay`


Monthly cost of living and housing estimates from the Teal V3.0 ONS Family Spending model (table A6). `expenditures` is newest month first.

Cost of living is age-adjusted from each payroll period's `identity_information.date_of_birth`, falling back to the user's `date_of_birth`. Housing is region-adjusted from each period's `identity_information.address.post_code`, falling back to the user's first `post_codes` entry that has an ONS region. Age is calculated as of the last day of each month.

Some months are left out of `expenditures`:

* Months whose net pay is zero or negative (refunds, reversals). They are skipped rather than returned as `0.00` or negative amounts.
* Months where neither the payroll period's postcode nor any of the user's `post_codes` has an ONS region (for example GY, JE, IM or an unknown postcode).

`404` returns `{ "errors": [...] }` with one string per missing or invalid input:

| Error | Cause |
| - | - |
| `User [<user_id>] not found` | No such user for this client |
| `[date_of_birth] param is missing on the user and its payslips` | No date of birth on a month's payroll period or on the user |
| `[post_codes] param is missing on the user and its payslips` | No postcode on a payroll period or on the user |
| `[date_of_birth] param <date> is after the payslip month` | Date of birth is after the month it applies to, for example in the future |
| `[date_of_birth] param <date> gives an age over 150, the ONS model limit` | Age is above the model's oldest band |
| `Postcode <postcode> has no ONS region; supported: England, Wales, Scotland, Northern Ireland` | No month has a postcode with an ONS region. One string per postcode tried |
| `payroll periods are missing` | No payroll periods with net pay and dates |
| `payroll periods have no positive net pay` | Every month has zero or negative net pay |


## OpenAPI

````yaml GET /expenditure/{user_id}/ons
openapi: 3.0.1
info:
  title: Payroll API
  description: A full flagged payroll api provided by Teal
  version: 1.0.0
servers:
  - url: https://api.sandbox.goteal.co
    description: Sandbox server for experiments
  - url: https://api.goteal.co
    description: Production server
security:
  - ApiKeyAuth: []
  - MemberBearerAuth: []
paths:
  /expenditure/{user_id}/ons:
    get:
      summary: Returns monthly ONS V3.0 affordability estimates for a user
      description: >
        Estimates cost of living and housing from the user's payroll periods,

        date of birth, and postcode using the Teal V3.0 ONS Family Spending

        model (table A6).


        Net pay is split across calendar months by overlapping days. Age is

        taken as of the last day of each month from that period's date of

        birth (else the user's) and adjusts cost of living only.

        Region (from each period's postcode prefix, else the user's first

        postcode that has an ONS region) adjusts housing only.


        Months are skipped, not returned, when their net pay is zero or

        negative (refunds, reversals) or when no postcode for the month has

        an ONS region (for example GY, JE, IM or an unknown postcode).


        `404` lists each missing or invalid input as its own string in

        `errors`:

        - `User [<user_id>] not found`

        - `[date_of_birth] param is missing on the user and its payslips`

        - `[post_codes] param is missing on the user and its payslips`

        - `[date_of_birth] param <date> is after the payslip month`

        - `[date_of_birth] param <date> gives an age over 150, the ONS model
        limit`

        - `Postcode <postcode> has no ONS region; supported: England, Wales,
        Scotland, Northern Ireland`
          (only when no month has a postcode with a region)
        - `payroll periods are missing`

        - `payroll periods have no positive net pay`
      parameters:
        - in: path
          name: user_id
          schema:
            type: string
            format: uuid
          required: true
          description: ID of the user to estimate expenditure for
      responses:
        '200':
          description: Monthly expenditure estimates
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OnsExpenditureResponse'
              example:
                expenditures:
                  - date: 2026-01-01T12:00Z
                    cost_of_living: '333.21'
                    housing: '372.96'
                    total: '706.17'
        '401':
          $ref: '#/components/responses/UnauthorizedApiKey'
        '404':
          description: User not found, or required inputs are missing or invalid
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                errors:
                  - >-
                    [date_of_birth] param is missing on the user and its
                    payslips
                  - '[post_codes] param is missing on the user and its payslips'
                  - payroll periods are missing
components:
  schemas:
    OnsExpenditureResponse:
      type: object
      required:
        - expenditures
      properties:
        expenditures:
          type: array
          description: One row per calendar month that has allocated net pay
          items:
            type: object
            required:
              - date
              - cost_of_living
              - housing
              - total
            properties:
              date:
                type: string
                format: date-time
                description: First day of the calendar month at UTC noon
                example: 2026-01-01T12:00Z
              cost_of_living:
                type: string
                description: Age-adjusted cost of living in pounds (2 decimals)
                example: '333.21'
              housing:
                type: string
                description: Region-adjusted housing in pounds (2 decimals)
                example: '372.96'
              total:
                type: string
                description: cost_of_living plus housing
                example: '706.17'
    Error:
      required:
        - errors
      type: object
      properties:
        errors:
          type: array
          items:
            type: string
          description: An array of messages describing the errors
          example:
            - The request is missing the required field `name`
    Unauthorized:
      type: object
      properties:
        errors:
          type: string
          description: An array of messages describing the errors
          example:
            - No X-API-KEY header provided or wrong value
  responses:
    UnauthorizedApiKey:
      description: Unauthorized if the X-API-KEY is not provided or is wrong
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Unauthorized'
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-KEY
    MemberBearerAuth:
      type: http
      scheme: bearer
      description: >-
        Bearer token for authentication. The token should be the one returned by
        the "/members/signin"

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.