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

# List cost lines

> List the cost lines visible to the caller.

This resource carries monetary values and is permissioned separately from the
cost sheets themselves, so a caller who can see that a job has been costed may
still not be able to see what it cost.

Filter by `cost_sheet` for the lines that make up one sheet's cost.




## OpenAPI

````yaml /api-reference/openapi.yaml get /v3/cost-lines
openapi: 3.1.0
info:
  contact:
    email: support@ctrl-hub.com
    name: Ctrl Hub
    url: https://www.ctrl-hub.com
  description: >
    Ctrl Hub is the all-in-one platform for high-risk industries like utilities,
    construction, infrastructure, and renewables. We help teams manage
    everything from risk assessments and HAVS exposure to vehicle and equipment
    checks, with a guaranteed minimum of 200% ROI.
  license:
    name: MIT License
    url: https://opensource.org/licenses/MIT
  summary: An API for managing your compliance and risk posture
  termsOfService: https://www.ctrl-hub.com/terms-conditions
  title: Ctrl Hub
  version: 1.0.0
servers:
  - description: Production
    url: https://api.ctrl-hub.com
  - description: Staging
    url: https://api.ctrl-hub.dev
  - description: Development
    url: https://api.ctrl-hub.run
security: []
tags:
  - description: |
      Actions are follow-ups assigned to users and teams, produced manually or
      by domain producers such as the data-capture workflow runner.
    name: Actions
  - description: |
      Audit events are the events that are logged by the system.
    name: Audit Events
  - description: |
      View the platform's health and availability.
    name: Status
  - description: >
      User-owned dashboards composed of cards on a fixed-slot bento layout.
      Cards come from a per-domain registry; the API stores their config as
      opaque JSON.
    name: Dashboards
  - description: >
      A record of documents the platform generated and handed over, retained as
      evidence of what

      was issued. `subject_type` says what kind of records an export covers, and
      it is what decides

      how the request is authorised: there is no export permission of its own,
      so if you can see the

      records you can export them.
    name: Exports
  - description: >
      Scheduled delivery of a saved query's results, as a CSV attachment or a
      summary email. Every run is retained with the file it sent, so what a
      recipient received stays retrievable. A report runs as the person who
      created it, under their own access.
    name: Reports
  - description: |
      Manage appointments for work to be carried out with your customers
    name: Customer Appointments
  - description: |
      Manage interactions you have with your customers
    name: Customer Interactions
  - description: |
      Manage accounts for your customers
    name: Customer Accounts and Contacts
  - description: |
      Qualifications are the skills and knowledge that an organisation requires.
    name: Qualifications
  - description: |
      Workflows allow you to automate your processes.
    name: Workflows
  - description: |
      Manage documents
    name: Documents
  - description: |
      Manage documents
    name: Folders
  - description: |
      Manage documents
    name: Document Reviews
  - description: |
      Manage feature configurations for an organisation.
    name: Feature Configurations
  - description: |
      Equipment are the physical assets that an organisation manages.
    name: Equipment
  - description: |
      The central cost register: time-versioned rate codes for material, labour,
      contractor and miscellaneous spend, used to cost work against the job
      hierarchy.
    name: Costs
  - description: >
      Locations are places an organisation manages, optionally classified by a
      location type.
    name: Locations
  - description: |
      Manage your forms and their schemas
    name: Forms, Schemas and Categories
  - description: |
      Create and view form submissions
    name: Submissions
  - description: |
      View the roles available in the system.
    name: IAM Roles
  - description: >
      IAM role groups can be assigned to principals to manage authorisation
      centrally.
    name: IAM Role Groups
  - description: |
      Manage service accounts which can access the API programmatically.
    name: Service Accounts
  - description: |
      Manage bridges between organisations.
    name: Bridges
  - description: |
      Manage settings for an organisation.
    name: Settings
  - description: |
      Manage teams within an organisation.
    name: Teams
  - description: |
      Manage job roles within an organisation.
    name: Job Roles
  - description: |
      Manage users and accounts.
    name: Users
  - description: |
      Invite and manage invitations to organisations.
    name: Invitations
  - description: >
      IAM grants are the asignment of roles or permissions to principals to
      manage resource access.
    name: IAM Grants
  - description: |
      View the permissions available in the system.
    name: IAM Permissions
  - description: |
      SSO providers are the identity providers for an organisation.
    name: SSO Providers
  - description: |
      Whoami returns information about the currently authenticated principal.
    name: Whoami
  - description: |
      Manage your images
    name: Images
  - description: >
      AI agent personas that synthesise data into role-specific intelligent
      briefings.
    name: Agents
  - description: |
      Briefings generated by AI agents, including reasoning traces.
    name: Briefings
  - description: >
      Organisations are the center point for most resources in the platform.
      Most other endpoints are subresources of an organisation.
    name: Organisations
  - description: |
      Permits managements, integrated with street manager.
    name: Permits
  - description: |
      Projects manage your work and governance.
    name: Projects
  - description: >
      Import templates allow users to save and reuse their CSV importer
      configuration as named templates.
    name: Import Templates
  - description: |
      Properties are the physical locations.
    name: Properties
  - description: |
      Search across schemes, work orders, and operations.
    name: Search
  - description: |
      Provides the API specification in JSON and YAML formats
    name: Specifications
  - description: |
      Streets are the physical roads.
    name: Streets
  - description: |
      Integration with street manager
    name: Street Manager
  - description: |
      Vehicles are the physical vehicles that an organisation manages.
    name: Vehicles
  - description: >
      Scheme contracts (also known as regions) group schemes allocated from the
      network to a contractor.
    name: Scheme Contracts
  - description: >
      Scheme shares allow you to share your schemes with other organisations
      across bridges.
    name: Scheme Shares
  - description: |
      Schemes are large programmes of work
    name: Schemes
  - description: |
      Work orders the component parts of a scheme.
    name: Work Orders
  - description: |
      Operations are the work to be carried out within work orders.
    name: Operations
externalDocs:
  description: More documentation and resources
  url: https://docs.ctrl-hub.com
paths:
  /v3/cost-lines:
    get:
      tags:
        - Costs
      summary: List cost lines
      description: >
        List the cost lines visible to the caller.


        This resource carries monetary values and is permissioned separately
        from the

        cost sheets themselves, so a caller who can see that a job has been
        costed may

        still not be able to see what it cost.


        Filter by `cost_sheet` for the lines that make up one sheet's cost.
      operationId: ListCostLines
      parameters:
        - $ref: '#/components/parameters/cost_lines_include'
        - $ref: '#/components/parameters/cost_line_filter'
        - $ref: '#/components/parameters/cost_lines_sort'
        - $ref: '#/components/parameters/limit'
        - $ref: '#/components/parameters/offset'
      responses:
        '200':
          $ref: '#/components/responses/ListCostLines'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorised'
        '500':
          $ref: '#/components/responses/InternalServerError'
      security:
        - Session: []
        - OAuth2: []
        - Cookie: []
components:
  parameters:
    cost_lines_include:
      name: include
      in: query
      description: A comma separated list of related resources to include.
      required: false
      style: form
      explode: false
      schema:
        type: array
        items:
          type: string
          enum:
            - organisation
            - cost_sheet
            - cost_item
            - cost_item_version
    cost_line_filter:
      name: filter
      in: query
      description: >
        Filters the response data based on the value provided.


        Available filters:


        - `id_in`: Filter by cost line ID(s)

        - `cost_sheet`: Filter by the cost sheet the line belongs to

        - `cost_item`: Filter by the rate code the line was priced from

        - `cost_item_version`: Filter by the register version the line was
        snapshotted from

        - `quantity`: Filter by quantity, in thousandths

        - `work_date`: Filter by the day the work was carried out

        - `created_at`: Filter on when the line was created

        - `line_type`: Filter by whether the line is an `estimated` line or an
        `actual` one

        - `cost_kit_application`: Filter by the application that produced the
        line, for
          the group of lines one use of a kit wrote

        Filter by `cost_sheet` for the lines that make up one sheet's cost.


        Filter by `cost_kit_application` for the group of lines a single
        application of

        a kit wrote — that is what lets a client show them together, and remove
        them

        together. Applying the same kit twice produces two groups with different
        ids.


        Filter by `line_type` to separate estimates from actuals — a listing of
        a

        sheet's costs that omits this filter returns both, and summing them
        without

        noticing conflates what was planned with what was spent. The two
        describe

        the same work twice, so the total means nothing.


        For more information on using named filters, see [the
        docs](https://docs.ctrl-hub.com/api-reference/features#filtering)
      required: false
      schema:
        type: string
        default: ''
    cost_lines_sort:
      in: query
      name: sort
      description: >
        A comma separated list of fields to sort by. Sorting by `work_date`
        gives a

        sheet's lines in the order the work was carried out.
      required: false
      style: form
      explode: false
      schema:
        type: array
        items:
          type: string
          enum:
            - work_date
            - '-work_date'
            - quantity
            - '-quantity'
            - created_at
            - '-created_at'
    limit:
      name: limit
      in: query
      description: Limit the number of resources returned by the API
      required: false
      schema:
        type: integer
        format: int64
        minimum: 1
        default: 100
    offset:
      name: offset
      in: query
      description: Offset the resources returned by the API
      required: false
      schema:
        type: integer
        format: int64
        minimum: 0
        default: 0
  responses:
    ListCostLines:
      description: List of cost lines.
      headers:
        Content-Type:
          $ref: '#/components/headers/content-type'
        Content-Length:
          $ref: '#/components/headers/content-length'
        X-Request-ID:
          $ref: '#/components/headers/x-request-id'
      content:
        application/vnd.api+json:
          schema:
            allOf:
              - type: object
                required:
                  - data
                properties:
                  data:
                    title: A list of cost lines
                    type: array
                    items:
                      $ref: '#/components/schemas/CostLine'
                  included:
                    $ref: '#/components/schemas/CostLineIncludes'
              - $ref: '#/components/schemas/DocumentMeta'
              - $ref: '#/components/schemas/JSONAPI'
    BadRequest:
      description: >
        There was an error with the request - this could be due to an invalid
        body, query parameters,

        or headers that were sent to the API.
      headers:
        Content-Type:
          $ref: '#/components/headers/content-type'
        Content-Length:
          $ref: '#/components/headers/content-length'
        X-Request-ID:
          $ref: '#/components/headers/x-request-id'
      content:
        application/vnd.api+json:
          schema:
            type: object
            properties:
              errors:
                type: array
                items:
                  $ref: '#/components/schemas/Error'
          example:
            id: 98ca4a78-b66f-4234-9719-aaf832ee6669
            status: '400'
            title: A validation error was encountered
            source:
              parameter: include
            meta:
              resource: wrong_value
    Unauthorised:
      description: Authentication failed
      headers:
        Content-Type:
          $ref: '#/components/headers/content-type'
        Content-Length:
          $ref: '#/components/headers/content-length'
        X-Request-ID:
          $ref: '#/components/headers/x-request-id'
      content:
        application/vnd.api+json:
          schema:
            type: object
            properties:
              errors:
                type: array
                items:
                  $ref: '#/components/schemas/Error'
          example:
            id: 05fc9c8d-73b9-4697-9337-57f7a567a48f
            status: '401'
            title: You are not authorised to access this resource
            detail: In order to access this resource, you need the 'admin' role.
            code: AUTH.001
    InternalServerError:
      description: There was a problem handling the request on the server side
      headers:
        Content-Type:
          $ref: '#/components/headers/content-type'
        Content-Length:
          $ref: '#/components/headers/content-length'
        X-Request-ID:
          $ref: '#/components/headers/x-request-id'
      content:
        application/vnd.api+json:
          schema:
            type: object
            properties:
              errors:
                type: array
                items:
                  $ref: '#/components/schemas/Error'
          example:
            id: fe9d9a69-f0a7-4fdc-bb2c-176027f316c5
            status: '500'
            title: Internal Server Error
            detail: An unexpected error occurred on the server.
  headers:
    content-type:
      description: The content type of the response
      schema:
        type: string
      example: application/vnd.api+json
    content-length:
      description: The length of the response body in bytes
      schema:
        type: integer
        format: int32
      example: 1234
    x-request-id:
      description: >-
        An ID that can be provided when reporting bugs to help identify the
        issue
      schema:
        type: string
      example: 8470f56af4cf25e22be08e72c70dbbdc
  schemas:
    CostLine:
      type: object
      description: >
        A cost line — one costed entry on a cost sheet: a quantity of a rate
        code,

        priced at the rate that was in force on the day the work was done.


        The pricing is a snapshot, on both sides. `rate` and `amount` are
        resolved

        once, on write, from the register version in force on `work_date`, and
        that

        version is referenced by the `cost_item_version` relationship. `margin`
        and

        `charge` — what the line sells for — are frozen at the same moment. None
        of

        them are ever recomputed: revising a rate or a margin in the register
        changes

        what future lines cost and sell for, and leaves every existing line
        exactly as

        it was recorded.


        This resource carries monetary values and is permissioned separately
        from the

        cost sheet it belongs to.
      required:
        - id
        - type
        - attributes
        - meta
        - relationships
      properties:
        id:
          type: string
          format: uuid
          description: The unique identifier of the cost line.
        type:
          type: string
          const: cost-lines
        attributes:
          $ref: '#/components/schemas/CostLineAttributes'
        meta:
          $ref: '#/components/schemas/CostLineMeta'
        relationships:
          $ref: '#/components/schemas/CostLineRelationships'
    CostLineIncludes:
      type: array
      description: >
        Related resources that can be included when a cost line is returned.


        `cost-items` and `cost-item-versions` are both optional on a line, so
        asking

        for either simply yields nothing for the lines that have none — an
        ad-hoc line

        has no rate code, and a line priced on the job has no register version
        behind

        its rate. Neither is an error, and a request for both across a mixed
        collection

        comes back with whatever exists.
      items:
        discriminator:
          propertyName: type
          mapping:
            organisations:
              $ref: '#/components/schemas/Organisation'
            cost-sheets:
              $ref: '#/components/schemas/CostSheet'
            cost-items:
              $ref: '#/components/schemas/CostItem'
            cost-item-versions:
              $ref: '#/components/schemas/CostItemVersion'
        oneOf:
          - $ref: '#/components/schemas/Organisation'
          - $ref: '#/components/schemas/CostSheet'
          - $ref: '#/components/schemas/CostItem'
          - $ref: '#/components/schemas/CostItemVersion'
    DocumentMeta:
      type: object
      description: Document level meta about the resources on the server in list endpoints.
      required:
        - meta
      properties:
        meta:
          type: object
          required:
            - pagination
          properties:
            features:
              $ref: '#/components/schemas/Features'
            pagination:
              $ref: '#/components/schemas/Pagination'
    JSONAPI:
      type: object
      description: JSON API response object
      required:
        - jsonapi
      properties:
        jsonapi:
          type: object
          required:
            - version
          properties:
            version:
              type: string
              description: The version of the JSON API specification
              examples:
                - '1.0'
    Error:
      type: object
      description: An error response
      properties:
        id:
          description: >-
            A unique identifier for this particular occurrence of the problem.
            If you encounter this, please provide us with the error ID and we
            can investigate it on our side.
          type: string
          format: uuid
          examples:
            - 05fc9c8d-73b9-4697-9337-57f7a567a48f
        status:
          description: >-
            The status code for the error. This might not match the HTTP status
            code if there are more that one errors to return with different
            status codes.
          type: string
          examples:
            - '401'
            - '500'
        title:
          description: A human readable title for the error.
          type: string
          examples:
            - You are not authorised to access this resource
        detail:
          description: >-
            Where there is more detail that we can provide outside of the title,
            we will provide it here.
          type: string
          examples:
            - In order to access this resource, you need the 'admin' role.
        code:
          description: >-
            A unique code for the error that may help us to diagnose the issue.
            Not all errors have codes, so this is usually empty.
          type: string
          examples:
            - AUTH.001
        source:
          description: A JSON object containing additional information about the error.
          type: object
          properties:
            pointer:
              description: >-
                A JSON Pointer to the value in the request that caused the
                error.
              type: string
              examples:
                - /data/attributes/email
            parameter:
              description: >-
                A string indicating which query parameter in the request caused
                the error.
              type: string
              examples:
                - include
      required:
        - id
        - status
        - title
    CostLineAttributes:
      type: object
      description: Attributes for a cost line
      required:
        - quantity
        - work_date
        - rate
        - amount
        - margin
        - margin_override
        - margin_source
        - charge
        - profit
        - line_type
        - cost_item_snapshot
      properties:
        line_type:
          allOf:
            - $ref: '#/components/schemas/CostLineType'
          description: >
            Whether this line is a planned estimate or a recorded actual. Set
            once

            when the line is created — defaulting to `actual` if the request
            omits

            it — and immutable afterwards: correcting one means deleting the
            line

            and entering it again, not changing this value in place.


            A line written before this field existed carries no stored value and

            reads as `actual` — every line recorded before estimates existed was

            one, by definition.
        quantity:
          type: integer
          format: int64
          description: >
            The quantity of the cost item, in thousandths. 3.5 hours is `3500`
            and

            12.75 metres is `12750`.


            Quantities are scaled integers rather than decimals for the same
            reason

            money is: the line's amount is the quantity multiplied by a rate, so
            a

            fractional quantity would put rounding error straight into a
            currency

            amount.
          examples:
            - 3500
        work_date:
          type: string
          format: date
          description: >
            The day the work was carried out. The rate is resolved from the
            register

            version in force on this date. Defaults to today when the request
            omits

            it.
          examples:
            - '2026-03-14'
        description:
          type: string
          description: An optional note describing what this line covers
          examples:
            - Two operatives, second reinstatement visit
        rate:
          allOf:
            - $ref: '#/components/schemas/Money'
          description: >
            The unit rate the line was priced at, frozen when the line was
            written.

            Revising the register later never changes it.


            Where it came from depends on the line. Where `cost_item_version` is
            set,

            it is a snapshot of the register version in force on `work_date`,
            resolved

            by the server. Where `cost_item_version` is null, the client
            supplied it —

            either pricing a rate code on this job, or recording an ad-hoc cost
            with no

            rate code at all.


            **A client can never change it.** An update may send it back
            unchanged so a

            client can round-trip the whole resource, but a different value is
            refused

            with `CH.004.216`: the amount, the charge and the profit were all
            struck

            against this number, so a line entered at the wrong price is deleted
            and

            entered again rather than corrected in place.


            **The server replaces it in exactly one case.** Moving `work_date`
            on a line

            that was priced from the register re-resolves the rate from the
            version in

            force on the new date — the line is now saying the work happened on
            a day a

            different rate applied, so the old snapshot was taken against the
            wrong

            version. A line whose rate was supplied has no version to re-resolve
            and

            keeps its rate whatever the date.


            So "frozen" means frozen against register revisions and against
            clients. It

            does not mean the number can never move: re-dating a register-priced
            line

            is a deliberate re-pricing, and it is the only one.
        amount:
          allOf:
            - $ref: '#/components/schemas/Money'
          description: >
            The line total: `rate` multiplied by `quantity`, rounded half away
            from

            zero, once, on the total. Resolved by the server; revising the
            register

            later never changes it.
          readOnly: true
        margin:
          allOf:
            - $ref: '#/components/schemas/CostMargin'
          description: >
            The markup the line was sold at — a snapshot of the margin resolved
            from

            the cost item when the line was written, its own if it set one,
            otherwise

            its category's default. Always present: unlike a cost item, a line
            always

            has a margin resolved.


            Resolved by the server. Revising a margin in the register changes
            what

            future work sells for and leaves recorded work alone, so correcting
            a

            margin that was wrong does not retrospectively fix lines already
            recorded

            against it.
          readOnly: true
        charge:
          allOf:
            - $ref: '#/components/schemas/Money'
          description: >
            What the line sells for: `rate` marked up by `margin`, multiplied by

            `quantity`, rounded half away from zero, once, on the total — the
            same

            arithmetic `amount` uses, so the cost and sell sides of a line never

            disagree. Resolved by the server; revising the register later never

            changes it.
          readOnly: true
        margin_override:
          oneOf:
            - $ref: '#/components/schemas/CostMargin'
            - type: 'null'
          description: >
            The markup pinned onto this line when it was recorded, overriding
            whatever

            the cost item says. Null where the line took the register's margin.


            This is the one part of a line's pricing a client sets. The amounts
            it

            produces — `charge`, and therefore `profit` — are still worked out
            by the

            server.
        margin_source:
          allOf:
            - $ref: '#/components/schemas/CostMarginSource'
          description: >
            Which of the three produced `margin` — the override pinned onto this
            line,

            the rate code's own margin, or its category's default. Read it with

            `margin_override` and `cost_item_snapshot.margin` to answer "why is
            this

            job selling at this markup" without comparing against the register
            and

            guessing.
          readOnly: true
        profit:
          allOf:
            - $ref: '#/components/schemas/Money'
          description: >
            `charge` less `amount` — what the line made. Zero where the line
            sells at

            cost, which a zero margin does; negative where it sells for less
            than it

            cost, which a credit line correcting an over-recorded quantity does.

            Derived from the two frozen amounts rather than stored, so it cannot

            disagree with them.
          readOnly: true
        cost_item_snapshot:
          allOf:
            - $ref: '#/components/schemas/CostItemSnapshot'
          description: >
            What this line bought, frozen alongside its price so the line
            records what

            was bought and not only what it cost.


            Where `cost_item` is set it is the rate code as it stood when the
            line was

            written, copied by the server and not settable by a client. Where

            `cost_item` is null the line is ad-hoc and the client supplied it —
            see

            `CostItemSnapshotWritable` for the part it may set, which is
            narrower than

            what is presented here.


            A caller cannot tell the two apart by reading this block;
            `cost_item` is

            what answers that, and an ad-hoc `code` is free text that may
            coincide with

            a real rate code.
        cost_kit:
          oneOf:
            - $ref: '#/components/schemas/CostKitSnapshot'
            - type: 'null'
          description: >
            The kit that wrote this line, frozen at the moment it was applied,
            and null

            on a line entered by hand.


            It is an attribute rather than a relationship on purpose: it records
            what

            was applied rather than pointing at something live. The kit may
            since have

            been renamed or deleted without that making this line wrong.
          readOnly: true
        cost_kit_application_id:
          type:
            - string
            - 'null'
          format: uuid
          description: >
            Groups the lines written by one application of a kit, so a client
            can show

            them together and remove them together. Null on a line entered by
            hand.


            Applying the same kit twice produces two groups with different ids:
            each is

            a separate event with its own work date, quantity and line type.
          readOnly: true
          examples:
            - 7f3a2c18-9d40-4b6e-8a51-c2e07b4f9d63
    CostLineMeta:
      type: object
      description: Metadata for a cost line
      required:
        - created_at
        - updated_at
      properties:
        created_at:
          type: string
          format: date-time
          description: The date and time when the cost line was created
        updated_at:
          type: string
          format: date-time
          description: The date and time when the cost line was last updated
    CostLineRelationships:
      type: object
      description: Relationships for a cost line
      properties:
        organisation:
          type: object
          required:
            - data
          properties:
            data:
              $ref: '#/components/schemas/OrganisationRelationship'
        cost_sheet:
          type: object
          required:
            - data
          properties:
            data:
              $ref: '#/components/schemas/CostSheetRelationship'
        cost_item:
          type: object
          description: >
            The rate code this line was written against, null on an ad-hoc line.


            This is the discriminator: a line is ad-hoc when this is null, and
            never

            because of anything in `cost_item_snapshot`. An ad-hoc line's code
            is free

            text and may coincide with a real rate code, so it can never be
            trusted to

            answer the question.
          required:
            - data
          properties:
            data:
              oneOf:
                - $ref: '#/components/schemas/CostItemRelationship'
                - type: 'null'
        cost_item_version:
          type: object
          description: >
            The register version the line's rate was snapshotted from, and what
            makes

            that price auditable. Resolved by the server from the cost item and

            `work_date`; it cannot be set by the client.


            Null wherever the rate was supplied rather than resolved — on an
            ad-hoc

            line, and on a rate code priced on this job. It is therefore also
            what says

            whether re-dating the line can re-price it: with no version behind
            the

            rate, there is no register reading to revisit.
          required:
            - data
          readOnly: true
          properties:
            data:
              oneOf:
                - $ref: '#/components/schemas/CostItemVersionRelationship'
                - type: 'null'
    Organisation:
      type: object
      description: An organisation
      required:
        - id
        - type
        - attributes
        - meta
      properties:
        id:
          type: string
          format: uuid
          description: The unique identifier of the organisation.
        type:
          type: string
          const: organisations
        attributes:
          $ref: '#/components/schemas/OrganisationAttributes'
        meta:
          $ref: '#/components/schemas/OrganisationMeta'
        relationships:
          $ref: '#/components/schemas/OrganisationRelationships'
    CostSheet:
      type: object
      description: >
        A cost sheet — the costing of one node of the job hierarchy.


        A sheet holds no money itself. The cost lives on its cost lines, which
        are a

        separately permissioned resource, so a principal may be allowed to see
        that a

        job has been costed without being allowed to see what it cost.


        The link to the node it costs is polymorphic: `cost_centre_type` names
        the

        resource type and `cost_centre_id` identifies the record.
      required:
        - id
        - type
        - attributes
        - meta
        - relationships
      properties:
        id:
          type: string
          format: uuid
          description: The unique identifier of the cost sheet.
        type:
          type: string
          const: cost-sheets
        attributes:
          $ref: '#/components/schemas/CostSheetAttributes'
        meta:
          $ref: '#/components/schemas/CostSheetMeta'
        relationships:
          $ref: '#/components/schemas/CostSheetRelationships'
    CostItem:
      type: object
      description: A cost item — a rate code in the central cost register
      required:
        - id
        - type
        - attributes
        - meta
        - relationships
      properties:
        id:
          type: string
          format: uuid
          description: The unique identifier of the cost item.
        type:
          type: string
          const: cost-items
        attributes:
          $ref: '#/components/schemas/CostItemAttributes'
        meta:
          $ref: '#/components/schemas/CostItemMeta'
        relationships:
          $ref: '#/components/schemas/CostItemRelationships'
    CostItemVersion:
      type: object
      description: >
        A cost item version — one time-bound rate for a cost item. A new version
        starts

        whenever the rate changes, so work costed after the fact can be priced
        at the

        rate that was in force on the day it was carried out.


        This resource carries monetary values and is permissioned separately
        from the

        cost item itself.
      required:
        - id
        - type
        - attributes
        - meta
        - relationships
      properties:
        id:
          type: string
          format: uuid
          description: The unique identifier of the cost item version.
        type:
          type: string
          const: cost-item-versions
        attributes:
          $ref: '#/components/schemas/CostItemVersionAttributes'
        meta:
          $ref: '#/components/schemas/CostItemVersionMeta'
        relationships:
          $ref: '#/components/schemas/CostItemVersionRelationships'
    Features:
      type: object
      description: Represents feature configurations of the API
      properties:
        include:
          type: object
          properties:
            options:
              type: array
              items:
                type: string
                examples:
                  - related.resource
    Pagination:
      type: object
      description: Represents pagination details for API responses
      required:
        - counts
        - current_page
        - offsets
        - requested
      properties:
        counts:
          type: object
          required:
            - pages
            - resources
          properties:
            pages:
              type: integer
              examples:
                - 1
            resources:
              type: integer
              examples:
                - 1
        current_page:
          type: integer
          examples:
            - 1
        offsets:
          type: object
          required:
            - next
            - previous
          properties:
            next:
              type:
                - integer
                - 'null'
              examples:
                - null
            previous:
              type:
                - integer
                - 'null'
              examples:
                - null
        requested:
          type: object
          required:
            - limit
            - offset
          properties:
            limit:
              type: integer
              examples:
                - 10
            offset:
              type: integer
              examples:
                - 0
    CostLineType:
      type: string
      description: >
        Whether a cost line is a planned estimate or a recorded actual.


        An estimate is what the work was planned to cost, entered up front,
        before

        the work happens. An actual is what the work did cost, entered once it
        has.

        Both are recorded the same way — pick a rate code, enter a quantity,

        optionally override the margin — and both snapshot their rate and margin
        at

        the moment they are entered, exactly alike. This makes an estimate a
        fixed

        baseline rather than a live re-pricing: it does not move if the register
        is

        revised later, so a variance against it reflects the job changing rather

        than the rate card changing.


        Immutable once set. Correcting a line entered under the wrong type means

        deleting it and entering it again, not flipping this value.
      enum:
        - estimated
        - actual
      examples:
        - actual
    Money:
      type: object
      description: >
        A monetary amount held in the currency's minor units (pence for GBP)
        alongside

        its ISO 4217 currency code. Amounts are never decimals, so repeated
        arithmetic

        cannot accumulate rounding error.
      required:
        - amount
        - currency
      properties:
        amount:
          type: integer
          format: int64
          description: >-
            The amount in the currency's minor units, for example 9250 for GBP
            92.50
          examples:
            - 9250
        currency:
          type: string
          description: The ISO 4217 alphabetic currency code
          minLength: 3
          maxLength: 3
          pattern: ^[A-Z]{3}$
          examples:
            - GBP
    CostMargin:
      type: object
      description: >
        The markup applied on top of a base cost to reach the charge (sell)
        rate.

        Exactly one of `percentage` or `fixed` is set, chosen by `type`.
      required:
        - type
      properties:
        type:
          type: string
          description: Whether the markup is a proportion of the cost or a flat amount
          enum:
            - percentage
            - fixed
        percentage:
          type:
            - integer
            - 'null'
          description: >
            The markup in basis points when `type` is `percentage` — 1200 is
            12.00%.

            Basis points keep a margin expressed to two decimal places exact.
          minimum: 0
          examples:
            - 1200
        fixed:
          oneOf:
            - $ref: '#/components/schemas/Money'
            - type: 'null'
          description: The flat amount added to the cost when `type` is `fixed`
    CostMarginSource:
      type: string
      description: >
        Where the margin frozen onto a cost line came from.


        `override` means the markup was pinned onto this line when it was
        recorded,

        overriding the rate code. `item` means the cost item set a margin of its
        own

        and that is what applied. `category` means the cost item set none, so
        the

        default for its category did.


        It is recorded rather than worked out on read because the answer moves

        underneath a line that has already been written: a rate code carrying no

        margin today may carry one tomorrow, and the line has to be able to say
        what

        produced its own number.


        `item` and `category` both mean the register decided, but they are not
        the

        same answer. `category` says nobody had set a margin on this rate code,
        so the

        default for its kind of spend supplied the number; `item` says somebody
        had

        made a decision about that code specifically.


        Both record what applied **when the line was written**. The margin on
        the line

        is frozen either way: revising a category default, or setting a margin
        on a

        rate code that had none, changes what future work sells at and leaves
        every

        line already recorded exactly as it was priced.
      enum:
        - override
        - item
        - category
      examples:
        - override
    CostItemSnapshot:
      type: object
      description: >
        The rate code as it stood when the cost line was written.


        A line already freezes what the work cost — the rate, the margin, the
        charge.

        This freezes what was bought: the code, the name and the classification
        that

        give those numbers meaning.


        It sits beside the `cost_item` relationship rather than replacing it.
        The

        relationship still says which rate code the line was written against, so
        the

        register stays navigable; this says what that rate code meant on the
        day. A

        client rendering a line therefore needs no second request, and shows
        what was

        actually bought rather than what the register has since been edited to
        say.


        It is a copy, not a view. Editing a rate code changes what future work
        is

        costed against and leaves recorded work exactly as it was recorded, the
        same

        rule the rate and margin already follow. Correcting a mistyped code or
        name

        does not fix lines already written against it; the remedy is to delete
        the

        line and enter it again.
      required:
        - code
        - name
        - description
        - category
        - metering
        - unit
        - margin
      properties:
        code:
          type: string
          description: >-
            The rate code's short identifier, as it read when the line was
            written.
          examples:
            - MAT-001
        name:
          type: string
          description: The rate code's name, as it read when the line was written.
          examples:
            - Cold lay tarmac
        description:
          type: string
          description: The rate code's description, as it read when the line was written.
        category:
          allOf:
            - $ref: '#/components/schemas/CostCategory'
          description: >
            The kind of spend the line is attributed to. Roll-up totals read
            this

            rather than the register, which is what stops recategorising a rate
            code

            moving money between the categories of jobs costed years ago.
        metering:
          allOf:
            - $ref: '#/components/schemas/CostMetering'
          description: How the rate code is measured.
        unit:
          type: string
          description: The unit the quantity is counted in.
          examples:
            - tonne
        margin:
          oneOf:
            - $ref: '#/components/schemas/CostMargin'
            - type: 'null'
          description: >
            The rate code's OWN margin, null where it set none and its
            category's

            default applied.


            This is not the margin the line was priced at — that is `margin`,
            already

            resolved and frozen, and it may be an override ignoring this
            entirely.

            This records what the register was offering, which is what makes

            `margin_source` `item` and `category` tellable apart.
    CostKitSnapshot:
      type: object
      description: >
        The kit a cost line was written by, frozen at the moment it was applied.


        It is a copy rather than a link. Renaming or deleting the kit afterwards
        does

        not change or break a line already recorded, and the id is a label
        rather than

        a foreign key — nothing resolves through it.
      required:
        - id
        - code
        - name
      properties:
        id:
          type: string
          format: uuid
          description: The identifier of the kit as it was when the line was written.
        code:
          type: string
          description: The kit's code at the time it was applied.
          examples:
            - KIT-002
        name:
          type: string
          description: The kit's name at the time it was applied.
          examples:
            - Footway reinstatement
    OrganisationRelationship:
      type: object
      description: Represents a relationship to an organisation
      required:
        - id
        - type
      properties:
        id:
          type: string
          format: uuid
          description: The unique identifier of the organisation
        type:
          type: string
          const: organisations
    CostSheetRelationship:
      type: object
      description: Represents a relationship to a cost sheet
      required:
        - id
        - type
      properties:
        id:
          type: string
          format: uuid
          description: The unique identifier of the cost sheet
        type:
          type: string
          const: cost-sheets
    CostItemRelationship:
      type: object
      description: Represents a relationship to a cost item
      required:
        - id
        - type
      properties:
        id:
          type: string
          format: uuid
          description: The unique identifier of the cost item
        type:
          type: string
          const: cost-items
    CostItemVersionRelationship:
      type: object
      description: Represents a relationship to a cost item version
      required:
        - id
        - type
      properties:
        id:
          type: string
          format: uuid
          description: The unique identifier of the cost item version
        type:
          type: string
          const: cost-item-versions
    OrganisationAttributes:
      type: object
      description: Attributes for an organisation
      required:
        - name
        - slug
        - sandbox
      properties:
        name:
          type: string
          description: The name of the organisation.
          examples:
            - Acme Construction Ltd
        slug:
          type: string
          description: The URL-friendly identifier for the organisation.
          examples:
            - acme-construction
        description:
          type: string
          description: A short description of the organisation.
          examples:
            - >-
              Leading construction company specializing in infrastructure
              projects
        sandbox:
          type: boolean
          description: Whether the organisation is a sandbox (i.e. a test organisation)
          examples:
            - false
        settings:
          type: object
          properties:
            teams:
              type: object
              description: Team-related settings for the organisation.
              properties:
                customer_representatives:
                  type: array
                  description: >
                    IDs of teams within this organisation whose members act as

                    customer service representatives. Each entry must reference
                    a

                    team that belongs to this organisation.
                  items:
                    type: string
                    format: uuid
                  uniqueItems: true
                  examples:
                    - - b234c567-8901-2345-6789-abcdef012345
    OrganisationMeta:
      type: object
      description: Meta information for an organisation
      required:
        - v3
        - status
      properties:
        created_at:
          type: string
          format: date-time
          description: The creation time of the organisation.
          examples:
            - '2023-01-15T10:30:00.000Z'
        updated_at:
          type: string
          format: date-time
          description: The last update time of the organisation.
          examples:
            - '2023-02-20T14:45:00.000Z'
        v3:
          type: boolean
          description: Whether the organisation is using version 3 exclusively.
          examples:
            - true
        status:
          type: string
          enum:
            - active
            - inactive
          default: active
          description: The current status of the organisation.
          examples:
            - active
        features:
          type: array
          description: Available features and their limits for the organisation.
          items:
            type: object
            properties:
              name:
                type: string
                description: The name of the feature.
                examples:
                  - advanced_reporting
              enabled:
                type: boolean
                description: Whether the feature is enabled.
                examples:
                  - true
              limit:
                type: integer
                description: The usage limit for this feature (if applicable).
                examples:
                  - 1000
    OrganisationRelationships:
      type: object
      description: Relationships for an organisation
      properties:
        users:
          type: object
          required:
            - data
          properties:
            data:
              type: array
              items:
                $ref: '#/components/schemas/UserRelationship'
        service_accounts:
          type: object
          required:
            - data
          properties:
            data:
              type: array
              items:
                $ref: '#/components/schemas/ServiceAccountRelationship'
        groups:
          type: object
          required:
            - data
          properties:
            data:
              type: array
              items:
                $ref: '#/components/schemas/UserGroupRelationship'
        teams:
          type: object
          required:
            - data
          properties:
            data:
              type: array
              items:
                $ref: '#/components/schemas/TeamRelationship'
        nomenclature:
          type: object
          required:
            - data
          properties:
            data:
              oneOf:
                - $ref: '#/components/schemas/NomenclatureRelationship'
                - type: 'null'
    CostSheetAttributes:
      type: object
      description: Attributes for a cost sheet
      required:
        - cost_centre_id
        - cost_centre_type
        - status
      properties:
        cost_centre_id:
          type: string
          format: uuid
          description: >
            The unique identifier of the node being costed. Read it together
            with

            `cost_centre_type`, which names the resource type it points at.
          examples:
            - 9c1d5e42-8f30-4a6b-b7c9-15e8d0a3f627
        cost_centre_type:
          $ref: '#/components/schemas/CostCentreType'
        status:
          $ref: '#/components/schemas/CostSheetStatus'
        notes:
          type: string
          description: Free text notes about the costing of this cost centre
          examples:
            - Priced from the March rate card, pending client approval.
        additional_margin:
          type:
            - integer
            - 'null'
          description: >
            An uplift on this sheet's cost, in basis points — 500 is 5.00%. Null
            where

            the sheet has none.


            It is taken as a percentage of cost, the same way a margin marks up
            a

            rate, and it moves charge and profit only — never the cost it is
            taken

            from. Taking it off the already marked-up charge instead would
            compound

            the sheet's uplift with each line's own margin. The money it
            produces is

            not on this resource: it appears in the roll-up, which is gated on
            the

            permission that hides monetary values. That is why the uplift is a

            percentage rather than a margin that could carry a fixed amount — a
            cost

            sheet is readable by principals who must never receive an amount.
          minimum: 0
          examples:
            - 500
    CostSheetMeta:
      type: object
      description: Metadata for a cost sheet
      required:
        - created_at
        - updated_at
      properties:
        created_at:
          type: string
          format: date-time
          description: The date and time when the cost sheet was created
        updated_at:
          type: string
          format: date-time
          description: The date and time when the cost sheet was last updated
    CostSheetRelationships:
      type: object
      description: Relationships for a cost sheet
      properties:
        organisation:
          type: object
          required:
            - data
          properties:
            data:
              $ref: '#/components/schemas/OrganisationRelationship'
    CostItemAttributes:
      type: object
      description: Attributes for a cost item
      required:
        - code
        - name
        - category
        - metering
        - unit
      properties:
        code:
          type: string
          description: The rate code, unique within the organisation
          examples:
            - MAT-001
        name:
          type: string
          description: The name of the cost item
          examples:
            - Cold lay tarmac
        description:
          type: string
          description: An optional description of the cost item
        category:
          $ref: '#/components/schemas/CostCategory'
        metering:
          $ref: '#/components/schemas/CostMetering'
        unit:
          type: string
          description: The unit the rate is priced against
          examples:
            - tonne
        margin:
          oneOf:
            - $ref: '#/components/schemas/CostMargin'
            - type: 'null'
          description: >
            The markup applied to the base rate. Null when the cost item does
            not set

            one, in which case the consuming application applies its own
            default.
    CostItemMeta:
      type: object
      description: Metadata for a cost item
      required:
        - created_at
        - updated_at
      properties:
        created_at:
          type: string
          format: date-time
          description: The date and time when the cost item was created
        updated_at:
          type: string
          format: date-time
          description: The date and time when the cost item was last updated
    CostItemRelationships:
      type: object
      description: Relationships for a cost item
      properties:
        organisation:
          type: object
          required:
            - data
          properties:
            data:
              $ref: '#/components/schemas/OrganisationRelationship'
    CostItemVersionAttributes:
      type: object
      description: Attributes for a cost item version
      required:
        - version
        - rate
        - effective_from
      properties:
        version:
          type: integer
          description: >
            The sequential version number within the cost item, oldest first.
            Assigned

            by the server; it cannot be set by the client.
          readOnly: true
          examples:
            - 2
        rate:
          $ref: '#/components/schemas/Money'
        effective_from:
          type: string
          format: date
          description: The first day the rate applies, inclusive
          examples:
            - '2026-01-01'
        effective_to:
          type:
            - string
            - 'null'
          format: date
          description: >
            The last day the rate applies, inclusive. Null while the version is
            open

            ended and still in force.
          examples:
            - '2026-12-31'
        note:
          type: string
          description: A short note on why the rate changed
          examples:
            - Annual price review
    CostItemVersionMeta:
      type: object
      description: Metadata for a cost item version
      required:
        - status
        - created_at
        - updated_at
      properties:
        status:
          $ref: '#/components/schemas/CostItemVersionStatus'
        created_at:
          type: string
          format: date-time
          description: The date and time when the cost item version was created
        updated_at:
          type: string
          format: date-time
          description: The date and time when the cost item version was last updated
    CostItemVersionRelationships:
      type: object
      description: Relationships for a cost item version
      properties:
        organisation:
          type: object
          required:
            - data
          properties:
            data:
              $ref: '#/components/schemas/OrganisationRelationship'
        cost_item:
          type: object
          required:
            - data
          properties:
            data:
              $ref: '#/components/schemas/CostItemRelationship'
    CostCategory:
      type: string
      description: The kind of spend a cost item represents
      enum:
        - material
        - labour
        - misc
        - contractor
    CostMetering:
      type: string
      description: |
        How a cost item is measured when it is applied to a job.

        - `time` — charged for the time spent, billed by the hour
        - `day_rate` — charged as a flat rate for each working day
        - `volume` — charged by the quantity of material used
        - `fixed` — a fixed charge applied once
      enum:
        - time
        - day_rate
        - volume
        - fixed
    UserRelationship:
      type: object
      description: Represents a relationship to a user
      required:
        - id
        - type
      properties:
        id:
          type: string
          format: uuid
          description: The unique identifier of the user
        type:
          type: string
          const: users
    ServiceAccountRelationship:
      type: object
      description: Represents a relationship to a service account
      required:
        - id
        - type
      properties:
        id:
          type: string
          format: uuid
          description: The unique identifier of the service account
        type:
          type: string
          const: service-accounts
    UserGroupRelationship:
      type: object
      description: Represents a relationship to a group
      required:
        - id
        - type
      properties:
        id:
          type: string
          format: uuid
          description: The unique identifier of the group
        type:
          type: string
          const: groups
    TeamRelationship:
      type: object
      description: Represents a relationship to a team
      required:
        - id
        - type
      properties:
        id:
          type: string
          format: uuid
          description: The unique identifier of the team
        type:
          type: string
          const: teams
    NomenclatureRelationship:
      type: object
      description: Represents a relationship to a nomenclature resource
      required:
        - id
        - type
      properties:
        id:
          type: string
          format: uuid
          description: The unique identifier of the nomenclature resource
        type:
          type: string
          const: nomenclature
    CostCentreType:
      type: string
      description: >
        The level of the job hierarchy a cost sheet is attached to. Each value
        is the

        JSON:API resource type of the entity the sheet costs, so a sheet's
        target can

        be resolved from the `cost_centre_type` / `cost_centre_id` pair without
        a

        lookup table.
      enum:
        - scheme-contracts
        - schemes
        - work-orders
        - operations
    CostSheetStatus:
      type: string
      description: |
        How far a cost sheet has progressed through costing.

        - `quoted` — a priced estimate that has not been agreed
        - `approved` — agreed, and the work can proceed
        - `completed` — finished, and its costs are final
      enum:
        - quoted
        - approved
        - completed
    CostItemVersionStatus:
      type: string
      description: |
        Where a rate version sits relative to today.

        - `active` — in force today
        - `scheduled` — agreed but not started yet
        - `superseded` — closed off by a later rate
      enum:
        - active
        - scheduled
        - superseded
  securitySchemes:
    Session:
      description: |
        Session token for authentication.
      in: header
      name: X-Session-Token
      type: apiKey
    OAuth2:
      description: |
        OAuth2 token for authentication.
      flows:
        clientCredentials:
          scopes: {}
          tokenUrl: https://auth.ctrl-hub.com/oauth2/token
      type: oauth2
    Cookie:
      description: |
        Cookie token for authentication.
      in: cookie
      name: ctrl_hub_session
      type: apiKey

````