openapi: 3.1.0
info:
  title: TabSync Partner API
  version: 0.9.0
  summary: Create and update hospitality orders, report outside payments and read TabSync settlement state.
  description: |
    Public version 1 API for EPOS providers and ISVs.

    This contract intentionally contains only the Partner-facing integration surface:
    service status, API-key validation, Partner setup-directory reads, Partner order
    creation/replacement, Partner order polling and Partner-reported external
    settlement. Device activation, Device registration and waiter-app payment flows
    are TabSync proprietary app estate endpoints and are documented separately.
  contact:
    name: TabSync developer support
    url: https://docs.tabsync.co.uk
  license:
    name: Proprietary
    identifier: LicenseRef-Proprietary
servers:
  - url: https://dev.api.tabsync.co.uk/v1
    description: Shared development
  - url: https://api.tabsync.co.uk/v1
    description: Production
tags:
  - name: System
    description: API discovery and service information.
  - name: Authentication
    description: Validate a Partner-provisioned API key and inspect its fixed access boundary.
  - name: Partner directory
    description: Discover active Customers, Locations and Areas visible to a Partner API key.
  - name: Partner orders
    description: Create, replace and poll TabSync's server-authoritative order and settlement projection.
  - name: Partner payments
    description: Report settlement that happened outside a TabSync device, such as a till payment.
paths:
  /:
    get:
      operationId: getApiStatus
      summary: Get API status
      description: >-
        Returns the API identity and published major version.


        ### Success response fields


        | Field | Type | Required | Notes |

        | --- | --- | --- | --- |

        | `name` | string | Yes | API product name. |

        | `version` | string | Yes | Published major API version. |

        | `status` | string | Yes | `available` means the API process is reachable. Allowed:
        `available` |

        | `documentation_url` | uri | Yes | Developer documentation URL for this environment. |
      tags:
        - System
      security: []
      responses:
        "200":
          description: API status returned successfully.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiStatus"
              examples:
                available:
                  summary: API is available
                  value:
                    name: TabSync API
                    version: v1
                    status: available
                    documentation_url: https://dev.docs.tabsync.co.uk
        "429":
          $ref: "#/components/responses/RateLimited"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
  /authentication:
    get:
      operationId: getAuthenticationContext
      summary: Validate an API key
      description: >-
        Validates the opaque Bearer API key and returns its server-derived Offering,

        Customer or Location boundary plus its sandbox/production environment. Use this

        endpoint as a non-mutating connectivity check after creating a key. The response

        contains no secret or Customer business data and must not be cached.


        ### Success response fields


        | Field | Type | Required | Notes |

        | --- | --- | --- | --- |

        | `api_key_id` | uuid | Yes | Safe key identifier for support. This is not the secret API
        key. |

        | `environment` | string | Yes | Environment fixed to this API key. Allowed: `sandbox`,
        `production` |

        | `scope` | object | Yes | Maximum boundary this API key can access. |

        | `scope.type` | string | Yes | Boundary type selected when the key was provisioned.
        Allowed: `offering`, `customer`, `location` |

        | `scope.id` | uuid | Yes | Boundary object ID selected when the key was provisioned. |
      tags:
        - Authentication
      security:
        - ApiKey: []
      responses:
        "200":
          description: The API key is valid and its access boundary is returned.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Cache-Control:
              description: Prevents storage of authentication-context responses.
              schema:
                type: string
                const: no-store, private
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AuthenticationContext"
              examples:
                sandboxKey:
                  summary: Valid sandbox API key
                  value:
                    api_key_id: 0198a8f4-6578-76bf-b689-dcac02a5bb36
                    environment: sandbox
                    scope:
                      type: offering
                      id: 0198a8e3-5384-7c58-a4b8-4a21fbf1fc65
        "401":
          $ref: "#/components/responses/Unauthorised"
        "429":
          $ref: "#/components/responses/RateLimited"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
  /customers:
    get:
      operationId: listPartnerCustomers
      summary: List scoped Customers
      description: >-
        Returns active Customer records visible to the authenticated Partner API

        key. The key's Offering, Customer or Location scope is always the maximum

        access boundary; query filters can only narrow the result set. Use this

        endpoint to discover the `customer_id` values needed when mapping EPOS

        setup data to TabSync.


        ### Query parameters


        | Name | Type | Required | Notes |

        | --- | --- | --- | --- |

        | `offering_id` | uuid | No | Return only Customers with at least one active Location
        assigned to this Offering. |

        | `customer_id` | uuid | No | Return a specific Customer when it is already inside the
        API-key boundary. |

        | `location_id` | uuid | No | Return only the Customer owning this active Location when it
        is inside the API-key boundary. |

        | `limit` | integer | No | Maximum number of Customers returned on this page. Default: `50`
        Range: 1–100 |

        | `cursor` | string | No | Opaque cursor returned as `next_cursor` by the previous page.
        Length: 1–2048 |


        ### Success response fields


        | Field | Type | Required | Notes |

        | --- | --- | --- | --- |

        | `data` | array<object> | Yes | Customer records for this page. |

        | `data[].id` | uuid | Yes | TabSync Customer ID. Use this as `customer_id` when filtering
        directory requests. |

        | `data[].name` | string | Yes | Customer name held in TabSync. |

        | `data[].external_reference` | string / null | Yes | Partner-side Customer reference, when
        one was supplied in the portal. |

        | `data[].default_currency` | string | Yes | Default ISO 4217 currency for this Customer's
        Locations. Pattern: `^[A-Z]{3}$` |

        | `data[].status` | string | Yes | Directory responses currently expose only active
        Customers. Allowed: `active` |

        | `data[].created_at` | date-time | Yes | Time the Customer was created in TabSync. |

        | `data[].updated_at` | date-time | Yes | Time the Customer record last changed in TabSync.
        |

        | `next_cursor` | string / null | Yes | Opaque cursor for the next page, or `null` when no
        further records are available. |
      tags:
        - Partner directory
      security:
        - ApiKey: []
      parameters:
        - name: offering_id
          in: query
          required: false
          description: Return only Customers with at least one active Location assigned to this Offering.
          schema:
            $ref: "#/components/schemas/Uuid"
        - name: customer_id
          in: query
          required: false
          description: Return a specific Customer when it is already inside the API-key boundary.
          schema:
            $ref: "#/components/schemas/Uuid"
        - name: location_id
          in: query
          required: false
          description: Return only the Customer owning this active Location when it is inside the API-key
            boundary.
          schema:
            $ref: "#/components/schemas/Uuid"
        - name: limit
          in: query
          required: false
          description: Maximum number of Customers returned on this page.
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 50
        - name: cursor
          in: query
          required: false
          description: Opaque cursor returned as `next_cursor` by the previous page.
          schema:
            type: string
            minLength: 1
            maxLength: 2048
      responses:
        "200":
          description: The visible Customer records were returned.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Cache-Control:
              schema:
                type: string
                const: no-store, private
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PartnerCustomerPage"
              examples:
                cafeCustomer:
                  summary: Customer linked to the authenticated key
                  value:
                    data:
                      - id: 0198cc42-5d34-71cb-a36e-7343b2bb3000
                        name: Cafe Cappuccino
                        external_reference: CAFE-CAPPUCCINO
                        default_currency: GBP
                        status: active
                        created_at: 2026-08-16T09:05:00+00:00
                        updated_at: 2026-08-16T09:05:00+00:00
                    next_cursor: null
        "401":
          $ref: "#/components/responses/Unauthorised"
        "422":
          description: A filter, limit or cursor failed validation.
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
        "429":
          $ref: "#/components/responses/RateLimited"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
  /locations:
    get:
      operationId: listPartnerLocations
      summary: List scoped Locations
      description: >-
        Returns active Locations visible to the authenticated Partner API key.

        Each Location includes its pinned active Offering Version so the ISV can

        choose the correct `location_id` before posting an order. Query filters

        never expand the key's fixed boundary.


        ### Query parameters


        | Name | Type | Required | Notes |

        | --- | --- | --- | --- |

        | `offering_id` | uuid | No | Return only Locations assigned to this Offering. |

        | `customer_id` | uuid | No | Return only Locations belonging to this Customer. |

        | `location_id` | uuid | No | Return a specific Location when it is already inside the
        API-key boundary. |

        | `limit` | integer | No | Maximum number of Locations returned on this page. Default: `50`
        Range: 1–100 |

        | `cursor` | string | No | Opaque cursor returned as `next_cursor` by the previous page.
        Length: 1–2048 |


        ### Success response fields


        | Field | Type | Required | Notes |

        | --- | --- | --- | --- |

        | `data` | array<object> | Yes | Location records for this page. |

        | `data[].id` | uuid | Yes | TabSync Location ID. Send this as `location_id` when creating
        or replacing an order. |

        | `data[].customer_id` | uuid | Yes | Customer that owns this Location. |

        | `data[].name` | string | Yes | Location name shown in TabSync. |

        | `data[].external_reference` | string / null | Yes | Partner-side Location reference, when
        one was supplied in the portal. |

        | `data[].currency` | string | Yes | ISO 4217 currency. Order and payment currency must
        match this value. Pattern: `^[A-Z]{3}$` |

        | `data[].time_zone` | string | Yes | IANA time zone used for local operational views at
        this Location. |

        | `data[].status` | string | Yes | Directory responses currently expose only active
        Locations. Allowed: `active` |

        | `data[].offering` | object / null | Yes | Active Offering Version pinned to this Location,
        or `null` until configured. |

        | `data[].offering.id` | uuid | Yes | Offering ID. Use this as `offering_id` when filtering
        directory requests. |

        | `data[].offering.name` | string | Yes | Partner-defined Offering name. |

        | `data[].offering.version_id` | uuid | Yes | Exact Offering Version ID assigned to the
        Location. |

        | `data[].offering.version` | string | Yes | Human-readable Offering Version label. |

        | `data[].created_at` | date-time | Yes | Time the Location was created in TabSync. |

        | `data[].updated_at` | date-time | Yes | Time the Location record last changed in TabSync.
        |

        | `next_cursor` | string / null | Yes | Opaque cursor for the next page, or `null` when no
        further records are available. |
      tags:
        - Partner directory
      security:
        - ApiKey: []
      parameters:
        - name: offering_id
          in: query
          required: false
          description: Return only Locations assigned to this Offering.
          schema:
            $ref: "#/components/schemas/Uuid"
        - name: customer_id
          in: query
          required: false
          description: Return only Locations belonging to this Customer.
          schema:
            $ref: "#/components/schemas/Uuid"
        - name: location_id
          in: query
          required: false
          description: Return a specific Location when it is already inside the API-key boundary.
          schema:
            $ref: "#/components/schemas/Uuid"
        - name: limit
          in: query
          required: false
          description: Maximum number of Locations returned on this page.
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 50
        - name: cursor
          in: query
          required: false
          description: Opaque cursor returned as `next_cursor` by the previous page.
          schema:
            type: string
            minLength: 1
            maxLength: 2048
      responses:
        "200":
          description: The visible Location records were returned.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Cache-Control:
              schema:
                type: string
                const: no-store, private
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PartnerLocationPage"
              examples:
                cafeLocations:
                  summary: Location linked to an Offering Version
                  value:
                    data:
                      - id: 0198cc42-5d34-71cb-a36e-7343b2bb3001
                        customer_id: 0198cc42-5d34-71cb-a36e-7343b2bb3000
                        name: Cafe Cappuccino Guilsborough
                        external_reference: CAFE-GUI
                        currency: GBP
                        time_zone: Europe/London
                        status: active
                        offering:
                          id: 0198cc42-5d34-71cb-a36e-7343b2bb3100
                          name: Cafe Connect
                          version_id: 0198cc42-5d34-71cb-a36e-7343b2bb3101
                          version: "1.0"
                        created_at: 2026-08-16T09:06:00+00:00
                        updated_at: 2026-08-16T09:06:00+00:00
                    next_cursor: null
        "401":
          $ref: "#/components/responses/Unauthorised"
        "422":
          description: A filter, limit or cursor failed validation.
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
        "429":
          $ref: "#/components/responses/RateLimited"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
  /areas:
    get:
      operationId: listPartnerAreas
      summary: List scoped Areas
      description: >-
        Returns active Areas visible to the authenticated Partner API key. Areas

        are the lowest setup level used by TabSync order ingestion; `POST /orders`

        requires an `area_id` that belongs to the submitted `location_id` and to

        the key's fixed access boundary.


        ### Query parameters


        | Name | Type | Required | Notes |

        | --- | --- | --- | --- |

        | `offering_id` | uuid | No | Return only Areas under Locations assigned to this Offering. |

        | `customer_id` | uuid | No | Return only Areas belonging to this Customer. |

        | `location_id` | uuid | No | Return only Areas belonging to this Location. |

        | `limit` | integer | No | Maximum number of Areas returned on this page. Default: `50`
        Range: 1–100 |

        | `cursor` | string | No | Opaque cursor returned as `next_cursor` by the previous page.
        Length: 1–2048 |


        ### Success response fields


        | Field | Type | Required | Notes |

        | --- | --- | --- | --- |

        | `data` | array<object> | Yes | Area records for this page. |

        | `data[].id` | uuid | Yes | TabSync Area ID. Send this as `area_id` when creating or
        replacing an order. |

        | `data[].customer_id` | uuid | Yes | Customer that owns this Area. |

        | `data[].location_id` | uuid | Yes | Location that contains this Area. |

        | `data[].name` | string | Yes | Area name shown in TabSync. |

        | `data[].external_reference` | string / null | Yes | Partner-side Area reference, when one
        was supplied in the portal. |

        | `data[].status` | string | Yes | Directory responses currently expose only active Areas.
        Allowed: `active` |

        | `data[].display_order` | integer | Yes | Portal/app display ordering for Areas in the same
        Location. Range: 0–unbounded |

        | `data[].created_at` | date-time | Yes | Time the Area was created in TabSync. |

        | `data[].updated_at` | date-time | Yes | Time the Area record last changed in TabSync. |

        | `next_cursor` | string / null | Yes | Opaque cursor for the next page, or `null` when no
        further records are available. |
      tags:
        - Partner directory
      security:
        - ApiKey: []
      parameters:
        - name: offering_id
          in: query
          required: false
          description: Return only Areas under Locations assigned to this Offering.
          schema:
            $ref: "#/components/schemas/Uuid"
        - name: customer_id
          in: query
          required: false
          description: Return only Areas belonging to this Customer.
          schema:
            $ref: "#/components/schemas/Uuid"
        - name: location_id
          in: query
          required: false
          description: Return only Areas belonging to this Location.
          schema:
            $ref: "#/components/schemas/Uuid"
        - name: limit
          in: query
          required: false
          description: Maximum number of Areas returned on this page.
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 50
        - name: cursor
          in: query
          required: false
          description: Opaque cursor returned as `next_cursor` by the previous page.
          schema:
            type: string
            minLength: 1
            maxLength: 2048
      responses:
        "200":
          description: The visible Area records were returned.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Cache-Control:
              schema:
                type: string
                const: no-store, private
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PartnerAreaPage"
              examples:
                cafeAreas:
                  summary: Area for order ingestion
                  value:
                    data:
                      - id: 0198cc42-5d34-71cb-a36e-7343b2bb3002
                        customer_id: 0198cc42-5d34-71cb-a36e-7343b2bb3000
                        location_id: 0198cc42-5d34-71cb-a36e-7343b2bb3001
                        name: Main area
                        external_reference: MAIN
                        status: active
                        display_order: 0
                        created_at: 2026-08-16T09:07:00+00:00
                        updated_at: 2026-08-16T09:07:00+00:00
                    next_cursor: null
        "401":
          $ref: "#/components/responses/Unauthorised"
        "422":
          description: A filter, limit or cursor failed validation.
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
        "429":
          $ref: "#/components/responses/RateLimited"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
  /orders:
    post:
      operationId: createPartnerOrder
      summary: Create Partner order projection
      description: >-
        Creates a new TabSync order projection for a Partner-supplied EPOS order.

        The API key supplies the maximum Offering, Customer or Location boundary;

        the submitted `location_id` and `area_id` must resolve inside that boundary.

        Partner, Customer, environment and pinned Offering Version are derived by

        TabSync and cannot be supplied or overridden by the caller.


        The request may be total-only or itemised. When item lines are present,

        their gross `total_amount` values must add up to the order `amounts.total`.

        `status` may be `open` or `cancelled`; callers do not close an order by

        label. TabSync reports `closed` only when the settlement projection has no

        payable balance, normally after TabSync Device payments or

        Partner-reported external payments.


        The endpoint is idempotent by `Idempotency-Key`. Retrying the exact same

        body with the same key returns the same order projection and does not

        create another order. Reusing the key with different content returns

        `409`.


        ### Headers


        | Name | Type | Required | Notes |

        | --- | --- | --- | --- |

        | `Idempotency-Key` | string | Yes | Partner-generated retry key for this order creation
        request. Length: 8–128 Pattern: `^[\x21-\x7E]+$` |


        ### Request body fields


        | Field | Type | Required | Notes |

        | --- | --- | --- | --- |

        | `external_reference` | string | Yes | Case-sensitive EPOS order/check reference, unique
        within the Location. Length: 1–128 |

        | `location_id` | uuid | Yes | Location returned by `GET /locations`. The API key must allow
        this Location. |

        | `area_id` | uuid | Yes | Area returned by `GET /areas`. It must belong to the submitted
        Location. |

        | `table` | object / null | No | Optional table/check information. Omit or send `null` for
        counter, collection or takeaway orders. |

        | `table.reference` | string | Yes | Lookup value used by the app. Keep it stable for the
        life of the order. Length: 1–64 |

        | `table.display_name` | string / null | No | Friendly label shown to staff. If omitted,
        TabSync can display the reference. Length: 0–120 |

        | `status` | string | No | Source operational state. `closed` is not accepted here; TabSync
        derives closure from captured settlement. Allowed: `open`, `cancelled` Default: `open` |

        | `source_version` | string | Yes | Partner source revision for optimistic diagnostics and
        reconciliation. Length: 1–128 |

        | `currency` | string | Yes | Must match the Location currency. Pattern: `^[A-Z]{3}$` |

        | `amounts` | object | Yes | Gross order amounts in minor units. |

        | `amounts.subtotal` | integer | Yes | Net/gross subtotal before tax, service charge and
        discount according to the Partner's model. Range: 0–100000000 |

        | `amounts.tax` | integer | Yes | Tax amount included in the order total. Range: 0–100000000
        |

        | `amounts.service_charge` | integer | No | Service charge included in the payable total.
        Defaults to `0`. Default: `0` Range: 0–100000000 |

        | `amounts.discount` | integer | No | Discount deducted from the payable total. Defaults to
        `0`. Default: `0` Range: 0–100000000 |

        | `amounts.total` | integer | Yes | Total principal amount due before TabSync records any
        payments. Range: 0–100000000 |

        | `lines` | array<object> | No | Optional itemised lines. When present and non-empty, line
        external references must be unique within the payload and line gross totals must add up to
        `amounts.total`. |

        | `lines[].external_reference` | string | Yes | Partner-side line reference, unique within
        this order payload. Length: 1–128 |

        | `lines[].name` | string | Yes | Item name shown in the TabSync app. Length: 1–255 |

        | `lines[].quantity` | string | Yes | Positive decimal quantity with up to three decimal
        places. Pattern: `^[0-9]{1,7}(\.[0-9]{1,3})?$` |

        | `lines[].unit_amount` | integer | Yes | Unit price in minor units. Range: 0–100000000 |

        | `lines[].total_amount` | integer | Yes | Gross line total in minor units. All line totals
        must add up to the order total. Range: 0–100000000 |

        | `lines[].tax_amount` | integer | Yes | Tax amount attributed to this line. Range:
        0–100000000 |

        | `lines[].category` | string / null | No | Optional category shown in the TabSync app.
        Length: 0–120 |

        | `opened_at` | date-time | Yes | Time the order/check was opened in the EPOS. |

        | `metadata` | object | No | Optional integration hints currently understood by TabSync. |

        | `metadata.covers` | integer | No | Number of covers/guests, when the EPOS has this value.
        Range: 1–999 |

        | `metadata.service_type` | string | No | Partner-defined service style such as `eat_in`,
        `takeaway` or `collection`. Length: 1–64 |


        ### Success response fields


        | Field | Type | Required | Notes |

        | --- | --- | --- | --- |

        | `id` | uuid | Yes | TabSync Order ID used for polling, replacement and external-payment
        reports. |

        | `external_reference` | string | Yes | Partner-side order/check reference supplied on the
        latest write. Length: 1–128 |

        | `table` | object / null | Yes | Table/check information supplied by the Partner, or `null`
        when none was supplied. |

        | `table.reference` | string | Yes | Partner table lookup value. Length: 1–64 |

        | `table.display_name` | string / null | Yes | Friendly table/check label shown in TabSync.
        Length: 0–120 |

        | `status` | string | Yes | TabSync-derived operational status. `closed` means the financial
        projection has no balance due. Allowed: `open`, `closed`, `cancelled` |

        | `payment_status` | string | Yes | TabSync-derived payment state based on captured
        principal, refunds and the current order total. Allowed: `unpaid`, `partially_paid`, `paid`,
        `partially_refunded`, `refunded` |

        | `source_version` | string | Yes | Latest Partner-supplied source revision held by TabSync
        for this order. Length: 1–128 |

        | `version` | integer | Yes | TabSync order version incremented when the held projection
        changes. Range: 1–unbounded |

        | `currency` | string | Yes | ISO 4217 currency for all monetary fields on this order.
        Pattern: `^[A-Z]{3}$` |

        | `amounts` | object | Yes | Current financial projection in minor units. |

        | `amounts.subtotal` | integer | Yes | Current order subtotal supplied by the Partner.
        Range: 0–unbounded |

        | `amounts.tax` | integer | Yes | Current tax amount supplied by the Partner. Range:
        0–unbounded |

        | `amounts.service_charge` | integer | Yes | Current service charge supplied by the Partner.
        Range: 0–unbounded |

        | `amounts.discount` | integer | Yes | Current discount supplied by the Partner. Range:
        0–unbounded |

        | `amounts.total` | integer | Yes | Current principal amount due before captured payments
        and refunds. Range: 0–unbounded |

        | `amounts.captured_principal` | integer | Yes | Captured amount that reduces the order
        balance. Range: 0–unbounded |

        | `amounts.captured_tip` | integer | Yes | Captured gratuity recorded for reconciliation. It
        does not reduce the order balance. Range: 0–unbounded |

        | `amounts.refunded_principal` | integer | Yes | Principal already refunded. Range:
        0–unbounded |

        | `amounts.refunded_tip` | integer | Yes | Tip already refunded. Range: 0–unbounded |

        | `amounts.balance_due` | integer | Yes | Remaining principal due. `0` means TabSync
        considers the current order projection paid. Range: 0–unbounded |

        | `lines` | array<object> | Yes | Current itemised lines held by TabSync. Empty when the
        Partner posted a total-only order. |

        | `lines[].id` | uuid | Yes | TabSync line ID used in payment allocation responses. |

        | `lines[].external_reference` | string | Yes | Partner-side line reference supplied in the
        order write. Length: 1–128 |

        | `lines[].name` | string | Yes | Item name supplied by the Partner. Length: 1–255 |

        | `lines[].quantity` | number | Yes | Quantity supplied by the Partner. Range:
        0.001–unbounded |

        | `lines[].unit_amount` | integer | Yes | Unit price in minor units. Range: 0–unbounded |

        | `lines[].total_amount` | integer | Yes | Gross line total in minor units. Range:
        0–unbounded |

        | `lines[].tax_amount` | integer | Yes | Tax amount attributed to this line. Range:
        0–unbounded |

        | `lines[].paid_amount` | integer | Yes | Captured principal presented against this line.
        Open orders use captured line allocations; financially closed orders present every current
        line as fully paid. Range: 0–unbounded |

        | `lines[].remaining_amount` | integer | Yes | Remaining principal presented for this line.
        This is zero for every current line once the order is financially closed. Range: 0–unbounded
        |

        | `lines[].payment_status` | string | Yes | Presented line payment state. Once the order is
        financially closed, every current line is presented as paid. Allowed: `unpaid`,
        `partially_paid`, `paid` |

        | `lines[].category` | string / null | Yes | Category supplied by the Partner, or `null`.
        Length: 0–120 |

        | `payments` | array<object> | Yes | Captured and in-flight settlement records known to
        TabSync. |

        | `payments[].id` | uuid | Yes | TabSync payment/attempt ID. |

        | `payments[].source` | string | Yes | `tabsync_device` was taken through the TabSync app;
        `external` was reported by the Partner. Allowed: `tabsync_device`, `external` |

        | `payments[].external_reference` | string / null | Yes | Partner/provider payment
        reference, when available. Length: 0–128 |

        | `payments[].method` | string | Yes | Payment method reported by the source. Allowed:
        `cash`, `card`, `other` |

        | `payments[].status` | string | Yes | Only `captured` payments reduce the order balance.
        Allowed: `requires_confirmation`, `captured`, `failed`, `cancelled` |

        | `payments[].allocation_type` | string | Yes | How the principal amount was allocated to
        the order. Allowed: `full`, `partial_amount`, `item_selection` |

        | `payments[].currency` | string | Yes | ISO 4217 currency for this payment. Pattern:
        `^[A-Z]{3}$` |

        | `payments[].amounts` | object | Yes | Payment amounts in minor units. |

        | `payments[].amounts.principal` | integer | Yes | Captured principal that can reduce the
        order balance. Range: 0–unbounded |

        | `payments[].amounts.tip` | integer | Yes | Captured gratuity. Range: 0–unbounded |

        | `payments[].amounts.total` | integer | Yes | Principal plus tip. Range: 0–unbounded |

        | `payments[].trust_level` | string / null | Yes | Evidence source for the captured result.
        Allowed: `client_reported`, `partner_reported`, `null` |

        | `payments[].device` | object / null | Yes | Device that recorded the payment, or `null`
        for Partner-reported external payments. |

        | `payments[].device.id` | uuid | Yes | TabSync Device ID. |

        | `payments[].device.name` | string | Yes | Device name shown in the Partner portal. |

        | `payments[].captured_at` | string / null | Yes | Time the payment was captured, when
        known. |

        | `payments[].reported_at` | string / null | Yes | Time the payment result was reported to
        TabSync, when different from capture time. |

        | `payments[].created_at` | date-time | Yes | Time the payment record was created in
        TabSync. |

        | `payments[].allocations` | array<object> | Yes | Principal allocation lines used to
        explain how this payment reduced the balance. |

        | `payments[].allocations[].target_type` | string | Yes | `order_line` points at a line;
        `order_remainder` applies to the remaining balance without a line. Allowed: `order_line`,
        `order_remainder` |

        | `payments[].allocations[].order_line_id` | string / null | Yes | Line ID for `order_line`
        allocations, otherwise `null`. |

        | `payments[].allocations[].principal_amount` | integer | Yes | Principal amount allocated
        to this target. Range: 0–unbounded |

        | `opened_at` | date-time | Yes | Time the order/check was opened in the EPOS. |

        | `settled_at` | string / null | Yes | Time TabSync first calculated the order as fully
        settled, or `null` while open/unpaid. |

        | `created_at` | date-time | Yes | Time TabSync first created this order projection. |

        | `updated_at` | date-time | Yes | Time the order projection or settlement state last
        changed. |
      tags:
        - Partner orders
      security:
        - ApiKey: []
      parameters:
        - name: Idempotency-Key
          in: header
          required: true
          description: Partner-generated retry key for this order creation request.
          schema:
            type: string
            minLength: 8
            maxLength: 128
            pattern: ^[\x21-\x7E]+$
          example: create-cc-gui-1010
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PartnerOrderWrite"
            examples:
              cafeOrder:
                summary: Café table order with item lines
                value:
                  external_reference: CC-GUI-1010
                  location_id: 0198cc42-5d34-71cb-a36e-7343b2bb3001
                  area_id: 0198cc42-5d34-71cb-a36e-7343b2bb3002
                  table:
                    reference: "14"
                    display_name: Table 14
                  status: open
                  source_version: epos-1
                  currency: GBP
                  amounts:
                    subtotal: 775
                    tax: 155
                    service_charge: 0
                    discount: 0
                    total: 930
                  lines:
                    - external_reference: CC-GUI-1010-L1
                      name: Flat white
                      quantity: "2"
                      unit_amount: 325
                      total_amount: 650
                      tax_amount: 108
                      category: Coffee
                    - external_reference: CC-GUI-1010-L2
                      name: Pain au chocolat
                      quantity: "1"
                      unit_amount: 280
                      total_amount: 280
                      tax_amount: 47
                      category: Bakery
                  opened_at: 2026-08-16T11:55:00+00:00
                  metadata:
                    covers: 2
                    service_type: eat_in
      responses:
        "201":
          description: The order was created, or an identical idempotent retry returned the same projection.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Cache-Control:
              schema:
                type: string
                const: no-store, private
            Location:
              description: Relative path for retrieving the created order.
              schema:
                type: string
              example: /v1/orders/0198cc42-5d34-71cb-a36e-7343b2bb1010
            ETag:
              description: Entity tag derived from the order ID and current TabSync order version.
              schema:
                type: string
              example: '"order-0198cc42-5d34-71cb-a36e-7343b2bb1010-v1"'
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PartnerOrder"
              examples:
                createdOrder:
                  $ref: "#/components/examples/PartnerOrderCreated"
        "401":
          $ref: "#/components/responses/Unauthorised"
        "404":
          description: The supplied Location or Area does not exist inside the authenticated API-key boundary.
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
        "409":
          description: The idempotency key was reused, the order reference already exists, or a domain
            conflict prevents creation.
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              examples:
                duplicateReference:
                  value:
                    type: https://docs.tabsync.co.uk/problems/order_reference_already_exists
                    title: Order reference already exists
                    status: 409
                    detail: An order with this external reference already exists for the Location.
                    instance: urn:tabsync:request:req_0198cc43-f586-7f47-8bc1-2023700350c1
                    code: order_reference_already_exists
                idempotencyKeyReused:
                  value:
                    type: https://docs.tabsync.co.uk/problems/idempotency_key_reused
                    title: Idempotency key reused
                    status: 409
                    detail: Use a new Idempotency-Key for a different order request.
                    instance: urn:tabsync:request:req_0198cc43-f586-7f47-8bc1-2023700350c2
                    code: idempotency_key_reused
        "422":
          description: The order body or idempotency header failed validation.
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
        "429":
          $ref: "#/components/responses/RateLimited"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
  /orders/{order_id}:
    get:
      operationId: getPartnerOrder
      summary: Get Partner order settlement projection
      description: >-
        Returns TabSync's current view of one order inside the authenticated

        API key's fixed Offering, Customer or Location scope. Unknown order IDs

        and orders outside the API-key boundary return the same `404` response

        so a caller cannot enumerate another Partner's data.


        `status` and `payment_status` are TabSync-derived from the order total,

        captured principal payments and principal refunds. A Partner cannot close

        an order by label alone. If Partner-reported till payments or TabSync app

        payments cover or exceed the amount due, this response reports the order

        as `closed` and `paid`. If a later order update adds unpaid items or

        otherwise increases the amount due, the same projection reports the order

        as `open` again until the new balance is covered.


        Use the returned `ETag` with `If-None-Match` when polling. A matching tag

        returns `304 Not Modified` with no response body. Responses are never

        cacheable by intermediaries.


        ### Path parameters


        | Name | Type | Required | Notes |

        | --- | --- | --- | --- |

        | `order_id` | uuid | Yes | Opaque TabSync UUID. Do not parse or construct it client-side. |


        ### Headers


        | Name | Type | Required | Notes |

        | --- | --- | --- | --- |

        | `If-None-Match` | string | No | Entity tag from a previous `GET /orders/{order_id}`
        response. |


        ### Success response fields


        | Field | Type | Required | Notes |

        | --- | --- | --- | --- |

        | `id` | uuid | Yes | TabSync Order ID used for polling, replacement and external-payment
        reports. |

        | `external_reference` | string | Yes | Partner-side order/check reference supplied on the
        latest write. Length: 1–128 |

        | `table` | object / null | Yes | Table/check information supplied by the Partner, or `null`
        when none was supplied. |

        | `table.reference` | string | Yes | Partner table lookup value. Length: 1–64 |

        | `table.display_name` | string / null | Yes | Friendly table/check label shown in TabSync.
        Length: 0–120 |

        | `status` | string | Yes | TabSync-derived operational status. `closed` means the financial
        projection has no balance due. Allowed: `open`, `closed`, `cancelled` |

        | `payment_status` | string | Yes | TabSync-derived payment state based on captured
        principal, refunds and the current order total. Allowed: `unpaid`, `partially_paid`, `paid`,
        `partially_refunded`, `refunded` |

        | `source_version` | string | Yes | Latest Partner-supplied source revision held by TabSync
        for this order. Length: 1–128 |

        | `version` | integer | Yes | TabSync order version incremented when the held projection
        changes. Range: 1–unbounded |

        | `currency` | string | Yes | ISO 4217 currency for all monetary fields on this order.
        Pattern: `^[A-Z]{3}$` |

        | `amounts` | object | Yes | Current financial projection in minor units. |

        | `amounts.subtotal` | integer | Yes | Current order subtotal supplied by the Partner.
        Range: 0–unbounded |

        | `amounts.tax` | integer | Yes | Current tax amount supplied by the Partner. Range:
        0–unbounded |

        | `amounts.service_charge` | integer | Yes | Current service charge supplied by the Partner.
        Range: 0–unbounded |

        | `amounts.discount` | integer | Yes | Current discount supplied by the Partner. Range:
        0–unbounded |

        | `amounts.total` | integer | Yes | Current principal amount due before captured payments
        and refunds. Range: 0–unbounded |

        | `amounts.captured_principal` | integer | Yes | Captured amount that reduces the order
        balance. Range: 0–unbounded |

        | `amounts.captured_tip` | integer | Yes | Captured gratuity recorded for reconciliation. It
        does not reduce the order balance. Range: 0–unbounded |

        | `amounts.refunded_principal` | integer | Yes | Principal already refunded. Range:
        0–unbounded |

        | `amounts.refunded_tip` | integer | Yes | Tip already refunded. Range: 0–unbounded |

        | `amounts.balance_due` | integer | Yes | Remaining principal due. `0` means TabSync
        considers the current order projection paid. Range: 0–unbounded |

        | `lines` | array<object> | Yes | Current itemised lines held by TabSync. Empty when the
        Partner posted a total-only order. |

        | `lines[].id` | uuid | Yes | TabSync line ID used in payment allocation responses. |

        | `lines[].external_reference` | string | Yes | Partner-side line reference supplied in the
        order write. Length: 1–128 |

        | `lines[].name` | string | Yes | Item name supplied by the Partner. Length: 1–255 |

        | `lines[].quantity` | number | Yes | Quantity supplied by the Partner. Range:
        0.001–unbounded |

        | `lines[].unit_amount` | integer | Yes | Unit price in minor units. Range: 0–unbounded |

        | `lines[].total_amount` | integer | Yes | Gross line total in minor units. Range:
        0–unbounded |

        | `lines[].tax_amount` | integer | Yes | Tax amount attributed to this line. Range:
        0–unbounded |

        | `lines[].paid_amount` | integer | Yes | Captured principal presented against this line.
        Open orders use captured line allocations; financially closed orders present every current
        line as fully paid. Range: 0–unbounded |

        | `lines[].remaining_amount` | integer | Yes | Remaining principal presented for this line.
        This is zero for every current line once the order is financially closed. Range: 0–unbounded
        |

        | `lines[].payment_status` | string | Yes | Presented line payment state. Once the order is
        financially closed, every current line is presented as paid. Allowed: `unpaid`,
        `partially_paid`, `paid` |

        | `lines[].category` | string / null | Yes | Category supplied by the Partner, or `null`.
        Length: 0–120 |

        | `payments` | array<object> | Yes | Captured and in-flight settlement records known to
        TabSync. |

        | `payments[].id` | uuid | Yes | TabSync payment/attempt ID. |

        | `payments[].source` | string | Yes | `tabsync_device` was taken through the TabSync app;
        `external` was reported by the Partner. Allowed: `tabsync_device`, `external` |

        | `payments[].external_reference` | string / null | Yes | Partner/provider payment
        reference, when available. Length: 0–128 |

        | `payments[].method` | string | Yes | Payment method reported by the source. Allowed:
        `cash`, `card`, `other` |

        | `payments[].status` | string | Yes | Only `captured` payments reduce the order balance.
        Allowed: `requires_confirmation`, `captured`, `failed`, `cancelled` |

        | `payments[].allocation_type` | string | Yes | How the principal amount was allocated to
        the order. Allowed: `full`, `partial_amount`, `item_selection` |

        | `payments[].currency` | string | Yes | ISO 4217 currency for this payment. Pattern:
        `^[A-Z]{3}$` |

        | `payments[].amounts` | object | Yes | Payment amounts in minor units. |

        | `payments[].amounts.principal` | integer | Yes | Captured principal that can reduce the
        order balance. Range: 0–unbounded |

        | `payments[].amounts.tip` | integer | Yes | Captured gratuity. Range: 0–unbounded |

        | `payments[].amounts.total` | integer | Yes | Principal plus tip. Range: 0–unbounded |

        | `payments[].trust_level` | string / null | Yes | Evidence source for the captured result.
        Allowed: `client_reported`, `partner_reported`, `null` |

        | `payments[].device` | object / null | Yes | Device that recorded the payment, or `null`
        for Partner-reported external payments. |

        | `payments[].device.id` | uuid | Yes | TabSync Device ID. |

        | `payments[].device.name` | string | Yes | Device name shown in the Partner portal. |

        | `payments[].captured_at` | string / null | Yes | Time the payment was captured, when
        known. |

        | `payments[].reported_at` | string / null | Yes | Time the payment result was reported to
        TabSync, when different from capture time. |

        | `payments[].created_at` | date-time | Yes | Time the payment record was created in
        TabSync. |

        | `payments[].allocations` | array<object> | Yes | Principal allocation lines used to
        explain how this payment reduced the balance. |

        | `payments[].allocations[].target_type` | string | Yes | `order_line` points at a line;
        `order_remainder` applies to the remaining balance without a line. Allowed: `order_line`,
        `order_remainder` |

        | `payments[].allocations[].order_line_id` | string / null | Yes | Line ID for `order_line`
        allocations, otherwise `null`. |

        | `payments[].allocations[].principal_amount` | integer | Yes | Principal amount allocated
        to this target. Range: 0–unbounded |

        | `opened_at` | date-time | Yes | Time the order/check was opened in the EPOS. |

        | `settled_at` | string / null | Yes | Time TabSync first calculated the order as fully
        settled, or `null` while open/unpaid. |

        | `created_at` | date-time | Yes | Time TabSync first created this order projection. |

        | `updated_at` | date-time | Yes | Time the order projection or settlement state last
        changed. |
      tags:
        - Partner orders
      security:
        - ApiKey: []
      parameters:
        - name: order_id
          in: path
          required: true
          schema:
            $ref: "#/components/schemas/Uuid"
          example: 0198cc42-5d34-71cb-a36e-7343b2bb1009
        - name: If-None-Match
          in: header
          required: false
          description: Entity tag from a previous `GET /orders/{order_id}` response.
          schema:
            type: string
          example: '"order-0198cc42-5d34-71cb-a36e-7343b2bb1009-v2"'
      responses:
        "200":
          description: The scoped order projection was returned.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Cache-Control:
              schema:
                type: string
                const: no-store, private
            ETag:
              description: Entity tag derived from the order ID and current TabSync order version.
              schema:
                type: string
              example: '"order-0198cc42-5d34-71cb-a36e-7343b2bb1009-v2"'
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PartnerOrder"
              examples:
                externallySettledOrder:
                  $ref: "#/components/examples/PartnerOrderExternallySettled"
        "304":
          description: The caller's cached representation is still current. The response body is empty.
          headers:
            Cache-Control:
              schema:
                type: string
                const: no-store, private
            ETag:
              schema:
                type: string
              example: '"order-0198cc42-5d34-71cb-a36e-7343b2bb1009-v2"'
        "401":
          $ref: "#/components/responses/Unauthorised"
        "404":
          description: No order exists inside the authenticated API-key boundary with that ID.
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
        "429":
          $ref: "#/components/responses/RateLimited"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
    put:
      operationId: replacePartnerOrder
      summary: Replace Partner order projection
      description: >-
        Replaces the current EPOS projection for an existing order. The caller

        sends the complete current payable projection rather than a partial patch,

        making amount and line replacement explicit.


        `If-Match` must contain the current order `ETag` returned by `GET`,

        `POST` or a previous `PUT`. Stale values return `412` with the current

        order version and ETag. `Idempotency-Key` protects the mutation from

        duplicate retries in the same way as `POST /orders`.


        The replacement cannot move an order to another Location or Area. It

        cannot remove a line that already has payment allocations in the current

        development schema; adding new lines or increasing the total is supported.

        After the replacement is saved, TabSync recalculates the settlement

        projection from captured principal payments. If the new total exceeds the

        captured principal, a previously closed order becomes open again.


        ### Path parameters


        | Name | Type | Required | Notes |

        | --- | --- | --- | --- |

        | `order_id` | uuid | Yes | Opaque TabSync UUID. Do not parse or construct it client-side. |


        ### Headers


        | Name | Type | Required | Notes |

        | --- | --- | --- | --- |

        | `Idempotency-Key` | string | Yes | Partner-generated retry key for this order replacement
        request. Length: 8–128 Pattern: `^[\x21-\x7E]+$` |

        | `If-Match` | string | Yes | Current entity tag from the latest order representation. |


        ### Request body fields


        | Field | Type | Required | Notes |

        | --- | --- | --- | --- |

        | `external_reference` | string | Yes | Case-sensitive EPOS order/check reference, unique
        within the Location. Length: 1–128 |

        | `location_id` | uuid | Yes | Location returned by `GET /locations`. The API key must allow
        this Location. |

        | `area_id` | uuid | Yes | Area returned by `GET /areas`. It must belong to the submitted
        Location. |

        | `table` | object / null | No | Optional table/check information. Omit or send `null` for
        counter, collection or takeaway orders. |

        | `table.reference` | string | Yes | Lookup value used by the app. Keep it stable for the
        life of the order. Length: 1–64 |

        | `table.display_name` | string / null | No | Friendly label shown to staff. If omitted,
        TabSync can display the reference. Length: 0–120 |

        | `status` | string | No | Source operational state. `closed` is not accepted here; TabSync
        derives closure from captured settlement. Allowed: `open`, `cancelled` Default: `open` |

        | `source_version` | string | Yes | Partner source revision for optimistic diagnostics and
        reconciliation. Length: 1–128 |

        | `currency` | string | Yes | Must match the Location currency. Pattern: `^[A-Z]{3}$` |

        | `amounts` | object | Yes | Gross order amounts in minor units. |

        | `amounts.subtotal` | integer | Yes | Net/gross subtotal before tax, service charge and
        discount according to the Partner's model. Range: 0–100000000 |

        | `amounts.tax` | integer | Yes | Tax amount included in the order total. Range: 0–100000000
        |

        | `amounts.service_charge` | integer | No | Service charge included in the payable total.
        Defaults to `0`. Default: `0` Range: 0–100000000 |

        | `amounts.discount` | integer | No | Discount deducted from the payable total. Defaults to
        `0`. Default: `0` Range: 0–100000000 |

        | `amounts.total` | integer | Yes | Total principal amount due before TabSync records any
        payments. Range: 0–100000000 |

        | `lines` | array<object> | No | Optional itemised lines. When present and non-empty, line
        external references must be unique within the payload and line gross totals must add up to
        `amounts.total`. |

        | `lines[].external_reference` | string | Yes | Partner-side line reference, unique within
        this order payload. Length: 1–128 |

        | `lines[].name` | string | Yes | Item name shown in the TabSync app. Length: 1–255 |

        | `lines[].quantity` | string | Yes | Positive decimal quantity with up to three decimal
        places. Pattern: `^[0-9]{1,7}(\.[0-9]{1,3})?$` |

        | `lines[].unit_amount` | integer | Yes | Unit price in minor units. Range: 0–100000000 |

        | `lines[].total_amount` | integer | Yes | Gross line total in minor units. All line totals
        must add up to the order total. Range: 0–100000000 |

        | `lines[].tax_amount` | integer | Yes | Tax amount attributed to this line. Range:
        0–100000000 |

        | `lines[].category` | string / null | No | Optional category shown in the TabSync app.
        Length: 0–120 |

        | `opened_at` | date-time | Yes | Time the order/check was opened in the EPOS. |

        | `metadata` | object | No | Optional integration hints currently understood by TabSync. |

        | `metadata.covers` | integer | No | Number of covers/guests, when the EPOS has this value.
        Range: 1–999 |

        | `metadata.service_type` | string | No | Partner-defined service style such as `eat_in`,
        `takeaway` or `collection`. Length: 1–64 |


        ### Success response fields


        | Field | Type | Required | Notes |

        | --- | --- | --- | --- |

        | `id` | uuid | Yes | TabSync Order ID used for polling, replacement and external-payment
        reports. |

        | `external_reference` | string | Yes | Partner-side order/check reference supplied on the
        latest write. Length: 1–128 |

        | `table` | object / null | Yes | Table/check information supplied by the Partner, or `null`
        when none was supplied. |

        | `table.reference` | string | Yes | Partner table lookup value. Length: 1–64 |

        | `table.display_name` | string / null | Yes | Friendly table/check label shown in TabSync.
        Length: 0–120 |

        | `status` | string | Yes | TabSync-derived operational status. `closed` means the financial
        projection has no balance due. Allowed: `open`, `closed`, `cancelled` |

        | `payment_status` | string | Yes | TabSync-derived payment state based on captured
        principal, refunds and the current order total. Allowed: `unpaid`, `partially_paid`, `paid`,
        `partially_refunded`, `refunded` |

        | `source_version` | string | Yes | Latest Partner-supplied source revision held by TabSync
        for this order. Length: 1–128 |

        | `version` | integer | Yes | TabSync order version incremented when the held projection
        changes. Range: 1–unbounded |

        | `currency` | string | Yes | ISO 4217 currency for all monetary fields on this order.
        Pattern: `^[A-Z]{3}$` |

        | `amounts` | object | Yes | Current financial projection in minor units. |

        | `amounts.subtotal` | integer | Yes | Current order subtotal supplied by the Partner.
        Range: 0–unbounded |

        | `amounts.tax` | integer | Yes | Current tax amount supplied by the Partner. Range:
        0–unbounded |

        | `amounts.service_charge` | integer | Yes | Current service charge supplied by the Partner.
        Range: 0–unbounded |

        | `amounts.discount` | integer | Yes | Current discount supplied by the Partner. Range:
        0–unbounded |

        | `amounts.total` | integer | Yes | Current principal amount due before captured payments
        and refunds. Range: 0–unbounded |

        | `amounts.captured_principal` | integer | Yes | Captured amount that reduces the order
        balance. Range: 0–unbounded |

        | `amounts.captured_tip` | integer | Yes | Captured gratuity recorded for reconciliation. It
        does not reduce the order balance. Range: 0–unbounded |

        | `amounts.refunded_principal` | integer | Yes | Principal already refunded. Range:
        0–unbounded |

        | `amounts.refunded_tip` | integer | Yes | Tip already refunded. Range: 0–unbounded |

        | `amounts.balance_due` | integer | Yes | Remaining principal due. `0` means TabSync
        considers the current order projection paid. Range: 0–unbounded |

        | `lines` | array<object> | Yes | Current itemised lines held by TabSync. Empty when the
        Partner posted a total-only order. |

        | `lines[].id` | uuid | Yes | TabSync line ID used in payment allocation responses. |

        | `lines[].external_reference` | string | Yes | Partner-side line reference supplied in the
        order write. Length: 1–128 |

        | `lines[].name` | string | Yes | Item name supplied by the Partner. Length: 1–255 |

        | `lines[].quantity` | number | Yes | Quantity supplied by the Partner. Range:
        0.001–unbounded |

        | `lines[].unit_amount` | integer | Yes | Unit price in minor units. Range: 0–unbounded |

        | `lines[].total_amount` | integer | Yes | Gross line total in minor units. Range:
        0–unbounded |

        | `lines[].tax_amount` | integer | Yes | Tax amount attributed to this line. Range:
        0–unbounded |

        | `lines[].paid_amount` | integer | Yes | Captured principal presented against this line.
        Open orders use captured line allocations; financially closed orders present every current
        line as fully paid. Range: 0–unbounded |

        | `lines[].remaining_amount` | integer | Yes | Remaining principal presented for this line.
        This is zero for every current line once the order is financially closed. Range: 0–unbounded
        |

        | `lines[].payment_status` | string | Yes | Presented line payment state. Once the order is
        financially closed, every current line is presented as paid. Allowed: `unpaid`,
        `partially_paid`, `paid` |

        | `lines[].category` | string / null | Yes | Category supplied by the Partner, or `null`.
        Length: 0–120 |

        | `payments` | array<object> | Yes | Captured and in-flight settlement records known to
        TabSync. |

        | `payments[].id` | uuid | Yes | TabSync payment/attempt ID. |

        | `payments[].source` | string | Yes | `tabsync_device` was taken through the TabSync app;
        `external` was reported by the Partner. Allowed: `tabsync_device`, `external` |

        | `payments[].external_reference` | string / null | Yes | Partner/provider payment
        reference, when available. Length: 0–128 |

        | `payments[].method` | string | Yes | Payment method reported by the source. Allowed:
        `cash`, `card`, `other` |

        | `payments[].status` | string | Yes | Only `captured` payments reduce the order balance.
        Allowed: `requires_confirmation`, `captured`, `failed`, `cancelled` |

        | `payments[].allocation_type` | string | Yes | How the principal amount was allocated to
        the order. Allowed: `full`, `partial_amount`, `item_selection` |

        | `payments[].currency` | string | Yes | ISO 4217 currency for this payment. Pattern:
        `^[A-Z]{3}$` |

        | `payments[].amounts` | object | Yes | Payment amounts in minor units. |

        | `payments[].amounts.principal` | integer | Yes | Captured principal that can reduce the
        order balance. Range: 0–unbounded |

        | `payments[].amounts.tip` | integer | Yes | Captured gratuity. Range: 0–unbounded |

        | `payments[].amounts.total` | integer | Yes | Principal plus tip. Range: 0–unbounded |

        | `payments[].trust_level` | string / null | Yes | Evidence source for the captured result.
        Allowed: `client_reported`, `partner_reported`, `null` |

        | `payments[].device` | object / null | Yes | Device that recorded the payment, or `null`
        for Partner-reported external payments. |

        | `payments[].device.id` | uuid | Yes | TabSync Device ID. |

        | `payments[].device.name` | string | Yes | Device name shown in the Partner portal. |

        | `payments[].captured_at` | string / null | Yes | Time the payment was captured, when
        known. |

        | `payments[].reported_at` | string / null | Yes | Time the payment result was reported to
        TabSync, when different from capture time. |

        | `payments[].created_at` | date-time | Yes | Time the payment record was created in
        TabSync. |

        | `payments[].allocations` | array<object> | Yes | Principal allocation lines used to
        explain how this payment reduced the balance. |

        | `payments[].allocations[].target_type` | string | Yes | `order_line` points at a line;
        `order_remainder` applies to the remaining balance without a line. Allowed: `order_line`,
        `order_remainder` |

        | `payments[].allocations[].order_line_id` | string / null | Yes | Line ID for `order_line`
        allocations, otherwise `null`. |

        | `payments[].allocations[].principal_amount` | integer | Yes | Principal amount allocated
        to this target. Range: 0–unbounded |

        | `opened_at` | date-time | Yes | Time the order/check was opened in the EPOS. |

        | `settled_at` | string / null | Yes | Time TabSync first calculated the order as fully
        settled, or `null` while open/unpaid. |

        | `created_at` | date-time | Yes | Time TabSync first created this order projection. |

        | `updated_at` | date-time | Yes | Time the order projection or settlement state last
        changed. |
      tags:
        - Partner orders
      security:
        - ApiKey: []
      parameters:
        - name: order_id
          in: path
          required: true
          schema:
            $ref: "#/components/schemas/Uuid"
          example: 0198cc42-5d34-71cb-a36e-7343b2bb1010
        - name: Idempotency-Key
          in: header
          required: true
          description: Partner-generated retry key for this order replacement request.
          schema:
            type: string
            minLength: 8
            maxLength: 128
            pattern: ^[\x21-\x7E]+$
          example: replace-cc-gui-1010-v2
        - name: If-Match
          in: header
          required: true
          description: Current entity tag from the latest order representation.
          schema:
            type: string
          example: '"order-0198cc42-5d34-71cb-a36e-7343b2bb1010-v2"'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PartnerOrderWrite"
            examples:
              addCake:
                summary: Replace after adding a cake line
                value:
                  external_reference: CC-GUI-1010
                  location_id: 0198cc42-5d34-71cb-a36e-7343b2bb3001
                  area_id: 0198cc42-5d34-71cb-a36e-7343b2bb3002
                  table:
                    reference: "14"
                    display_name: Table 14
                  status: open
                  source_version: epos-2
                  currency: GBP
                  amounts:
                    subtotal: 1046
                    tax: 209
                    service_charge: 0
                    discount: 0
                    total: 1255
                  lines:
                    - external_reference: CC-GUI-1010-L1
                      name: Flat white
                      quantity: "2"
                      unit_amount: 325
                      total_amount: 650
                      tax_amount: 108
                      category: Coffee
                    - external_reference: CC-GUI-1010-L2
                      name: Pain au chocolat
                      quantity: "1"
                      unit_amount: 280
                      total_amount: 280
                      tax_amount: 47
                      category: Bakery
                    - external_reference: CC-GUI-1010-L3
                      name: Millionaire shortbread
                      quantity: "1"
                      unit_amount: 325
                      total_amount: 325
                      tax_amount: 54
                      category: Cake
                  opened_at: 2026-08-16T11:55:00+00:00
                  metadata:
                    covers: 2
                    service_type: eat_in
      responses:
        "200":
          description: The order projection was replaced, or an identical idempotent retry returned the same
            projection.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Cache-Control:
              schema:
                type: string
                const: no-store, private
            ETag:
              description: Entity tag for the updated order projection.
              schema:
                type: string
              example: '"order-0198cc42-5d34-71cb-a36e-7343b2bb1010-v3"'
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PartnerOrder"
              examples:
                reopenedOrder:
                  $ref: "#/components/examples/PartnerOrderCreated"
        "401":
          $ref: "#/components/responses/Unauthorised"
        "404":
          description: No order exists inside the authenticated API-key boundary with that ID.
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
        "409":
          description: The idempotency key was reused, the order is locked for payment, or a line with payment
            allocations would be removed.
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              examples:
                locked:
                  value:
                    type: https://docs.tabsync.co.uk/problems/order_locked_for_payment
                    title: Order is locked for payment
                    status: 409
                    detail: The payable order cannot be replaced while a payment attempt is active.
                    instance: urn:tabsync:request:req_0198cc43-f586-7f47-8bc1-2023700350c3
                    code: order_locked_for_payment
                    retryable: true
        "412":
          description: The supplied `If-Match` does not match the current order version.
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              examples:
                staleVersion:
                  value:
                    type: https://docs.tabsync.co.uk/problems/version_conflict
                    title: Order version changed
                    status: 412
                    detail: Retrieve the current order projection and retry with its ETag.
                    instance: urn:tabsync:request:req_0198cc43-f586-7f47-8bc1-2023700350c4
                    code: version_conflict
                    order_id: 0198cc42-5d34-71cb-a36e-7343b2bb1010
                    current_version: 3
                    current_etag: '"order-0198cc42-5d34-71cb-a36e-7343b2bb1010-v3"'
        "422":
          description: The replacement body, idempotency header or `If-Match` header failed validation.
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
        "429":
          $ref: "#/components/responses/RateLimited"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
  /orders/{order_id}/external-payments:
    post:
      operationId: recordPartnerExternalPayment
      summary: Record Partner-reported external payment
      description: >-
        Records captured settlement evidence for money taken outside TabSync,

        such as a payment at the till. The endpoint does not accept a `closed`

        flag. TabSync adds the captured principal to the order ledger and then

        recalculates the order status from the financial projection.


        The API key must be allowed to access the order through its Offering,

        Customer or Location scope. The request is idempotent by

        `Idempotency-Key`: retrying the same body with the same key returns the

        existing recorded payment projection, while reusing the key with a

        different body returns `409`.


        `principal_amount` contributes to order settlement. `tip_amount` is

        recorded for reconciliation but does not reduce the order balance.


        ### Path parameters


        | Name | Type | Required | Notes |

        | --- | --- | --- | --- |

        | `order_id` | uuid | Yes | Opaque TabSync UUID. Do not parse or construct it client-side. |


        ### Headers


        | Name | Type | Required | Notes |

        | --- | --- | --- | --- |

        | `Idempotency-Key` | string | Yes | Partner-generated retry key for this external payment
        report. Length: 8–128 Pattern: `^[\x21-\x7E]+$` |


        ### Request body fields


        | Field | Type | Required | Notes |

        | --- | --- | --- | --- |

        | `payment_reference` | string | Yes | Partner-side payment identifier used for
        reconciliation. It is not a secret. Length: 1–128 |

        | `payment_method` | string | Yes | Payment method reported by the Partner for settlement
        taken outside TabSync. Allowed: `cash`, `card`, `other` |

        | `currency` | string | Yes | Must match the order currency. Pattern: `^[A-Z]{3}$` |

        | `principal_amount` | integer | Yes | Amount that settles the order balance, in the order
        currency's minor unit. Range: 1–100000000 |

        | `tip_amount` | integer | No | Optional gratuity amount recorded for reconciliation. It
        does not reduce the order balance. Default: `0` Range: 0–1000000 |

        | `captured_at` | date-time | No | Time the external system captured the payment. If
        omitted, TabSync records the report time. |

        | `reported_at` | date-time | No | Time the Partner system reported the payment to TabSync.
        Usually omit this unless replaying known historical evidence. |


        ### Success response fields


        | Field | Type | Required | Notes |

        | --- | --- | --- | --- |

        | `id` | uuid | Yes | TabSync Order ID used for polling, replacement and external-payment
        reports. |

        | `external_reference` | string | Yes | Partner-side order/check reference supplied on the
        latest write. Length: 1–128 |

        | `table` | object / null | Yes | Table/check information supplied by the Partner, or `null`
        when none was supplied. |

        | `table.reference` | string | Yes | Partner table lookup value. Length: 1–64 |

        | `table.display_name` | string / null | Yes | Friendly table/check label shown in TabSync.
        Length: 0–120 |

        | `status` | string | Yes | TabSync-derived operational status. `closed` means the financial
        projection has no balance due. Allowed: `open`, `closed`, `cancelled` |

        | `payment_status` | string | Yes | TabSync-derived payment state based on captured
        principal, refunds and the current order total. Allowed: `unpaid`, `partially_paid`, `paid`,
        `partially_refunded`, `refunded` |

        | `source_version` | string | Yes | Latest Partner-supplied source revision held by TabSync
        for this order. Length: 1–128 |

        | `version` | integer | Yes | TabSync order version incremented when the held projection
        changes. Range: 1–unbounded |

        | `currency` | string | Yes | ISO 4217 currency for all monetary fields on this order.
        Pattern: `^[A-Z]{3}$` |

        | `amounts` | object | Yes | Current financial projection in minor units. |

        | `amounts.subtotal` | integer | Yes | Current order subtotal supplied by the Partner.
        Range: 0–unbounded |

        | `amounts.tax` | integer | Yes | Current tax amount supplied by the Partner. Range:
        0–unbounded |

        | `amounts.service_charge` | integer | Yes | Current service charge supplied by the Partner.
        Range: 0–unbounded |

        | `amounts.discount` | integer | Yes | Current discount supplied by the Partner. Range:
        0–unbounded |

        | `amounts.total` | integer | Yes | Current principal amount due before captured payments
        and refunds. Range: 0–unbounded |

        | `amounts.captured_principal` | integer | Yes | Captured amount that reduces the order
        balance. Range: 0–unbounded |

        | `amounts.captured_tip` | integer | Yes | Captured gratuity recorded for reconciliation. It
        does not reduce the order balance. Range: 0–unbounded |

        | `amounts.refunded_principal` | integer | Yes | Principal already refunded. Range:
        0–unbounded |

        | `amounts.refunded_tip` | integer | Yes | Tip already refunded. Range: 0–unbounded |

        | `amounts.balance_due` | integer | Yes | Remaining principal due. `0` means TabSync
        considers the current order projection paid. Range: 0–unbounded |

        | `lines` | array<object> | Yes | Current itemised lines held by TabSync. Empty when the
        Partner posted a total-only order. |

        | `lines[].id` | uuid | Yes | TabSync line ID used in payment allocation responses. |

        | `lines[].external_reference` | string | Yes | Partner-side line reference supplied in the
        order write. Length: 1–128 |

        | `lines[].name` | string | Yes | Item name supplied by the Partner. Length: 1–255 |

        | `lines[].quantity` | number | Yes | Quantity supplied by the Partner. Range:
        0.001–unbounded |

        | `lines[].unit_amount` | integer | Yes | Unit price in minor units. Range: 0–unbounded |

        | `lines[].total_amount` | integer | Yes | Gross line total in minor units. Range:
        0–unbounded |

        | `lines[].tax_amount` | integer | Yes | Tax amount attributed to this line. Range:
        0–unbounded |

        | `lines[].paid_amount` | integer | Yes | Captured principal presented against this line.
        Open orders use captured line allocations; financially closed orders present every current
        line as fully paid. Range: 0–unbounded |

        | `lines[].remaining_amount` | integer | Yes | Remaining principal presented for this line.
        This is zero for every current line once the order is financially closed. Range: 0–unbounded
        |

        | `lines[].payment_status` | string | Yes | Presented line payment state. Once the order is
        financially closed, every current line is presented as paid. Allowed: `unpaid`,
        `partially_paid`, `paid` |

        | `lines[].category` | string / null | Yes | Category supplied by the Partner, or `null`.
        Length: 0–120 |

        | `payments` | array<object> | Yes | Captured and in-flight settlement records known to
        TabSync. |

        | `payments[].id` | uuid | Yes | TabSync payment/attempt ID. |

        | `payments[].source` | string | Yes | `tabsync_device` was taken through the TabSync app;
        `external` was reported by the Partner. Allowed: `tabsync_device`, `external` |

        | `payments[].external_reference` | string / null | Yes | Partner/provider payment
        reference, when available. Length: 0–128 |

        | `payments[].method` | string | Yes | Payment method reported by the source. Allowed:
        `cash`, `card`, `other` |

        | `payments[].status` | string | Yes | Only `captured` payments reduce the order balance.
        Allowed: `requires_confirmation`, `captured`, `failed`, `cancelled` |

        | `payments[].allocation_type` | string | Yes | How the principal amount was allocated to
        the order. Allowed: `full`, `partial_amount`, `item_selection` |

        | `payments[].currency` | string | Yes | ISO 4217 currency for this payment. Pattern:
        `^[A-Z]{3}$` |

        | `payments[].amounts` | object | Yes | Payment amounts in minor units. |

        | `payments[].amounts.principal` | integer | Yes | Captured principal that can reduce the
        order balance. Range: 0–unbounded |

        | `payments[].amounts.tip` | integer | Yes | Captured gratuity. Range: 0–unbounded |

        | `payments[].amounts.total` | integer | Yes | Principal plus tip. Range: 0–unbounded |

        | `payments[].trust_level` | string / null | Yes | Evidence source for the captured result.
        Allowed: `client_reported`, `partner_reported`, `null` |

        | `payments[].device` | object / null | Yes | Device that recorded the payment, or `null`
        for Partner-reported external payments. |

        | `payments[].device.id` | uuid | Yes | TabSync Device ID. |

        | `payments[].device.name` | string | Yes | Device name shown in the Partner portal. |

        | `payments[].captured_at` | string / null | Yes | Time the payment was captured, when
        known. |

        | `payments[].reported_at` | string / null | Yes | Time the payment result was reported to
        TabSync, when different from capture time. |

        | `payments[].created_at` | date-time | Yes | Time the payment record was created in
        TabSync. |

        | `payments[].allocations` | array<object> | Yes | Principal allocation lines used to
        explain how this payment reduced the balance. |

        | `payments[].allocations[].target_type` | string | Yes | `order_line` points at a line;
        `order_remainder` applies to the remaining balance without a line. Allowed: `order_line`,
        `order_remainder` |

        | `payments[].allocations[].order_line_id` | string / null | Yes | Line ID for `order_line`
        allocations, otherwise `null`. |

        | `payments[].allocations[].principal_amount` | integer | Yes | Principal amount allocated
        to this target. Range: 0–unbounded |

        | `opened_at` | date-time | Yes | Time the order/check was opened in the EPOS. |

        | `settled_at` | string / null | Yes | Time TabSync first calculated the order as fully
        settled, or `null` while open/unpaid. |

        | `created_at` | date-time | Yes | Time TabSync first created this order projection. |

        | `updated_at` | date-time | Yes | Time the order projection or settlement state last
        changed. |
      tags:
        - Partner payments
      security:
        - ApiKey: []
      parameters:
        - name: order_id
          in: path
          required: true
          schema:
            $ref: "#/components/schemas/Uuid"
          example: 0198cc42-5d34-71cb-a36e-7343b2bb1009
        - name: Idempotency-Key
          in: header
          required: true
          description: Partner-generated retry key for this external payment report.
          schema:
            type: string
            minLength: 8
            maxLength: 128
            pattern: ^[\x21-\x7E]+$
          example: till-payment-cc-gui-1009
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PartnerExternalPaymentCreate"
            examples:
              tillCardPayment:
                summary: Card payment captured at the till
                value:
                  payment_reference: TILL-CARD-1009
                  payment_method: card
                  currency: GBP
                  principal_amount: 930
                  tip_amount: 0
                  captured_at: 2026-08-16T12:15:00+00:00
                  reported_at: 2026-08-16T12:15:05+00:00
      responses:
        "201":
          description: The external payment was recorded or an identical idempotent retry returned the same
            order projection.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            Cache-Control:
              schema:
                type: string
                const: no-store, private
            ETag:
              description: Entity tag for the updated order projection.
              schema:
                type: string
              example: '"order-0198cc42-5d34-71cb-a36e-7343b2bb1009-v2"'
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PartnerOrder"
              examples:
                externallySettledOrder:
                  $ref: "#/components/examples/PartnerOrderExternallySettled"
        "401":
          $ref: "#/components/responses/Unauthorised"
        "404":
          description: No order exists inside the authenticated API-key boundary with that ID.
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
        "409":
          description: The idempotency key was reused with different content or the order has no outstanding
            payable balance.
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
              examples:
                orderNotPayable:
                  value:
                    type: https://docs.tabsync.co.uk/problems/order_not_payable
                    title: Order is not payable
                    status: 409
                    detail: The order has no outstanding payable balance.
                    instance: urn:tabsync:request:req_0198cc43-f586-7f47-8bc1-2023700350b5
                    code: order_not_payable
                idempotencyKeyReused:
                  value:
                    type: https://docs.tabsync.co.uk/problems/idempotency_key_reused
                    title: Idempotency key reused
                    status: 409
                    detail: Use a new Idempotency-Key for a different external payment report.
                    instance: urn:tabsync:request:req_0198cc43-f586-7f47-8bc1-2023700350b6
                    code: idempotency_key_reused
        "422":
          description: The payment body or idempotency header failed validation.
          content:
            application/problem+json:
              schema:
                $ref: "#/components/schemas/Problem"
        "429":
          $ref: "#/components/responses/RateLimited"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
components:
  securitySchemes:
    ApiKey:
      type: http
      scheme: bearer
      bearerFormat: TabSync opaque API key
      description: |
        An opaque API key beginning with `ts_sandbox_` or `ts_production_`.
        Send it only in the Authorization header. Never store a real API key
        in this OpenAPI file or the generated Postman collection.
  headers:
    RequestId:
      description: Opaque identifier used to correlate the request with support and operational logs.
      schema:
        type: string
        minLength: 1
        maxLength: 128
        examples:
          - req_0198b6f0-59c0-7c12-9b92-cb6e7f6cbf07
  responses:
    RateLimited:
      description: The caller exceeded an applicable request limit.
      headers:
        X-Request-Id:
          $ref: "#/components/headers/RequestId"
        Retry-After:
          description: Seconds the caller should wait before retrying.
          schema:
            type: integer
            minimum: 1
            examples:
              - 60
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/Problem"
          examples:
            rateLimitExceeded:
              value:
                type: https://docs.tabsync.co.uk/problems/rate_limit_exceeded
                title: Rate limit exceeded
                status: 429
                detail: Wait before trying the request again.
                instance: urn:tabsync:request:req_0198b6f0-59c0-7c12-9b92-cb6e7f6cbf07
                code: rate_limit_exceeded
    ServiceUnavailable:
      description: TabSync cannot currently reach a required dependency. The request has not completed and
        may be retried.
      headers:
        X-Request-Id:
          $ref: "#/components/headers/RequestId"
        Retry-After:
          description: Minimum number of seconds the caller should wait before retrying.
          schema:
            type: integer
            minimum: 1
            example: 60
        Cache-Control:
          description: Prevents an intermediary or client from retaining the temporary failure.
          schema:
            type: string
            const: no-store, private
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/Problem"
          examples:
            dependencyUnavailable:
              summary: A required service is temporarily unavailable
              value:
                type: https://docs.tabsync.co.uk/problems/service_unavailable
                title: Service temporarily unavailable
                status: 503
                detail: A required service is currently unavailable. Try again shortly.
                instance: urn:tabsync:request:req_0198b912-dfe8-78c7-a1b8-3d36eec1ee7c
                code: service_unavailable
    Unauthorised:
      description: The API key is missing, malformed, revoked or outside its active access boundary.
      headers:
        X-Request-Id:
          $ref: "#/components/headers/RequestId"
        WWW-Authenticate:
          description: Identifies the required bearer authentication scheme.
          schema:
            type: string
            const: Bearer realm="TabSync API"
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/Problem"
          examples:
            missingApiKey:
              summary: Authorization header is absent
              value:
                type: https://docs.tabsync.co.uk/problems/authentication_required
                title: Authentication required
                status: 401
                detail: Provide a TabSync API key in the Authorization Bearer header.
                instance: urn:tabsync:request:req_0198b6f0-59c0-7c12-9b92-cb6e7f6cbf07
                code: authentication_required
            invalidApiKey:
              summary: API key cannot authenticate
              value:
                type: https://docs.tabsync.co.uk/problems/invalid_api_key
                title: Invalid API key
                status: 401
                detail: The API key is invalid, revoked or outside its active environment and access boundary.
                instance: urn:tabsync:request:req_0198b6f0-59c0-7c12-9b92-cb6e7f6cbf07
                code: invalid_api_key
  examples:
    PartnerOrderCreated:
      summary: Open café order created by Partner API
      value:
        id: 0198cc42-5d34-71cb-a36e-7343b2bb1010
        external_reference: CC-GUI-1010
        table:
          reference: "14"
          display_name: Table 14
        status: open
        payment_status: unpaid
        source_version: epos-1
        version: 1
        currency: GBP
        amounts:
          subtotal: 775
          tax: 155
          service_charge: 0
          discount: 0
          total: 930
          captured_principal: 0
          captured_tip: 0
          refunded_principal: 0
          refunded_tip: 0
          balance_due: 930
        lines:
          - id: 0198cc42-5d34-71cb-a36e-7343b2bb2010
            external_reference: CC-GUI-1010-L1
            name: Flat white
            quantity: 2
            unit_amount: 325
            total_amount: 650
            tax_amount: 108
            paid_amount: 0
            remaining_amount: 650
            payment_status: unpaid
            category: Coffee
          - id: 0198cc42-5d34-71cb-a36e-7343b2bb2011
            external_reference: CC-GUI-1010-L2
            name: Pain au chocolat
            quantity: 1
            unit_amount: 280
            total_amount: 280
            tax_amount: 47
            paid_amount: 0
            remaining_amount: 280
            payment_status: unpaid
            category: Bakery
        payments: []
        opened_at: 2026-08-16T11:55:00+00:00
        settled_at: null
        created_at: 2026-08-16T12:01:18+00:00
        updated_at: 2026-08-16T12:01:18+00:00
    PartnerOrderExternallySettled:
      summary: Order settled by a Partner-reported till payment
      value:
        id: 0198cc42-5d34-71cb-a36e-7343b2bb1009
        external_reference: CC-GUI-1009
        table:
          reference: "9"
          display_name: Table 9
        status: closed
        payment_status: paid
        source_version: epos-1
        version: 2
        currency: GBP
        amounts:
          subtotal: 775
          tax: 155
          service_charge: 0
          discount: 0
          total: 930
          captured_principal: 930
          captured_tip: 0
          refunded_principal: 0
          refunded_tip: 0
          balance_due: 0
        lines:
          - id: 0198cc42-5d34-71cb-a36e-7343b2bb2001
            external_reference: CC-GUI-1009-L1
            name: Flat white
            quantity: 2
            unit_amount: 325
            total_amount: 650
            tax_amount: 108
            paid_amount: 650
            remaining_amount: 0
            payment_status: paid
            category: Coffee
          - id: 0198cc42-5d34-71cb-a36e-7343b2bb2002
            external_reference: CC-GUI-1009-L2
            name: Pain au chocolat
            quantity: 1
            unit_amount: 280
            total_amount: 280
            tax_amount: 47
            paid_amount: 280
            remaining_amount: 0
            payment_status: paid
            category: Bakery
        payments:
          - id: 0198cc43-15f8-7c2a-8f4b-6290eef1f001
            source: external
            external_reference: TILL-CARD-1009
            method: card
            status: captured
            allocation_type: full
            currency: GBP
            amounts:
              principal: 930
              tip: 0
              total: 930
            trust_level: partner_reported
            device: null
            captured_at: 2026-08-16T12:15:00+00:00
            reported_at: 2026-08-16T12:15:05+00:00
            created_at: 2026-08-16T12:15:05+00:00
            allocations:
              - target_type: order_line
                order_line_id: 0198cc42-5d34-71cb-a36e-7343b2bb2001
                principal_amount: 650
              - target_type: order_line
                order_line_id: 0198cc42-5d34-71cb-a36e-7343b2bb2002
                principal_amount: 280
        opened_at: 2026-08-16T11:55:00+00:00
        settled_at: 2026-08-16T12:15:00+00:00
        created_at: 2026-08-16T11:55:00+00:00
        updated_at: 2026-08-16T12:15:05+00:00
  schemas:
    ApiStatus:
      type: object
      description: Basic service identity response.
      additionalProperties: false
      required:
        - name
        - version
        - status
        - documentation_url
      properties:
        name:
          type: string
          const: TabSync API
          description: API product name.
        version:
          type: string
          const: v1
          description: Published major API version.
        status:
          type: string
          enum:
            - available
          description: "`available` means the API process is reachable."
        documentation_url:
          type: string
          format: uri
          description: Developer documentation URL for this environment.
    AuthenticationContext:
      type: object
      description: Non-secret description of the authenticated Partner API key.
      additionalProperties: false
      required:
        - api_key_id
        - environment
        - scope
      properties:
        api_key_id:
          $ref: "#/components/schemas/Uuid"
          description: Safe key identifier for support. This is not the secret API key.
        environment:
          type: string
          enum:
            - sandbox
            - production
          description: Environment fixed to this API key.
        scope:
          type: object
          description: Maximum boundary this API key can access.
          additionalProperties: false
          required:
            - type
            - id
          properties:
            type:
              type: string
              enum:
                - offering
                - customer
                - location
              description: Boundary type selected when the key was provisioned.
            id:
              $ref: "#/components/schemas/Uuid"
              description: Boundary object ID selected when the key was provisioned.
    Uuid:
      type: string
      format: uuid
      description: Opaque TabSync UUID. Do not parse or construct it client-side.
      examples:
        - 0198a135-b6d1-7b2a-a581-7fd487658c24
    PartnerCustomerPage:
      type: object
      description: Page of Customer records visible to the authenticated Partner API key.
      additionalProperties: false
      required:
        - data
        - next_cursor
      properties:
        data:
          type: array
          description: Customer records for this page.
          items:
            $ref: "#/components/schemas/PartnerCustomer"
        next_cursor:
          type:
            - string
            - "null"
          description: Opaque cursor for the next page, or `null` when no further records are available.
    Problem:
      type: object
      description: RFC 9457-compatible problem details.
      required:
        - type
        - title
        - status
        - detail
        - instance
        - code
      properties:
        type:
          type: string
          format: uri
        title:
          type: string
        status:
          type: integer
          minimum: 400
          maximum: 599
        detail:
          type: string
        instance:
          type: string
          format: uri
        code:
          type: string
          pattern: ^[a-z][a-z0-9_]*$
      additionalProperties: true
    PartnerLocationPage:
      type: object
      description: Page of Location records visible to the authenticated Partner API key.
      additionalProperties: false
      required:
        - data
        - next_cursor
      properties:
        data:
          type: array
          description: Location records for this page.
          items:
            $ref: "#/components/schemas/PartnerLocation"
        next_cursor:
          type:
            - string
            - "null"
          description: Opaque cursor for the next page, or `null` when no further records are available.
    PartnerAreaPage:
      type: object
      description: Page of Area records visible to the authenticated Partner API key.
      additionalProperties: false
      required:
        - data
        - next_cursor
      properties:
        data:
          type: array
          description: Area records for this page.
          items:
            $ref: "#/components/schemas/PartnerArea"
        next_cursor:
          type:
            - string
            - "null"
          description: Opaque cursor for the next page, or `null` when no further records are available.
    PartnerOrderWrite:
      type: object
      description: Complete payable EPOS order projection supplied by the Partner.
      additionalProperties: false
      required:
        - external_reference
        - location_id
        - area_id
        - source_version
        - currency
        - amounts
        - opened_at
      properties:
        external_reference:
          type: string
          minLength: 1
          maxLength: 128
          description: Case-sensitive EPOS order/check reference, unique within the Location.
          example: CC-GUI-1010
        location_id:
          $ref: "#/components/schemas/Uuid"
          description: Location returned by `GET /locations`. The API key must allow this Location.
        area_id:
          $ref: "#/components/schemas/Uuid"
          description: Area returned by `GET /areas`. It must belong to the submitted Location.
        table:
          oneOf:
            - $ref: "#/components/schemas/PartnerOrderTableInput"
            - type: "null"
          description: Optional table/check information. Omit or send `null` for counter, collection or
            takeaway orders.
        status:
          type: string
          enum:
            - open
            - cancelled
          default: open
          description: Source operational state. `closed` is not accepted here; TabSync derives closure from
            captured settlement.
        source_version:
          type: string
          minLength: 1
          maxLength: 128
          description: Partner source revision for optimistic diagnostics and reconciliation.
          example: epos-1
        currency:
          type: string
          pattern: ^[A-Z]{3}$
          description: Must match the Location currency.
          example: GBP
        amounts:
          $ref: "#/components/schemas/PartnerOrderWriteAmounts"
          description: Gross order amounts in minor units.
        lines:
          type: array
          maxItems: 200
          description: |
            Optional itemised lines. When present and non-empty, line external
            references must be unique within the payload and line gross totals
            must add up to `amounts.total`.
          items:
            $ref: "#/components/schemas/PartnerOrderLineWrite"
        opened_at:
          $ref: "#/components/schemas/DateTime"
          description: Time the order/check was opened in the EPOS.
        metadata:
          type: object
          description: Optional integration hints currently understood by TabSync.
          additionalProperties: false
          maxProperties: 16
          properties:
            covers:
              type: integer
              minimum: 1
              maximum: 999
              description: Number of covers/guests, when the EPOS has this value.
            service_type:
              type: string
              minLength: 1
              maxLength: 64
              description: Partner-defined service style such as `eat_in`, `takeaway` or `collection`.
    PartnerOrder:
      type: object
      description: TabSync's current server-authoritative order and settlement projection.
      additionalProperties: false
      required:
        - id
        - external_reference
        - table
        - status
        - payment_status
        - source_version
        - version
        - currency
        - amounts
        - lines
        - payments
        - opened_at
        - settled_at
        - created_at
        - updated_at
      properties:
        id:
          $ref: "#/components/schemas/Uuid"
          description: TabSync Order ID used for polling, replacement and external-payment reports.
        external_reference:
          type: string
          minLength: 1
          maxLength: 128
          description: Partner-side order/check reference supplied on the latest write.
        table:
          description: Table/check information supplied by the Partner, or `null` when none was supplied.
          oneOf:
            - $ref: "#/components/schemas/PartnerOrderTable"
            - type: "null"
        status:
          type: string
          enum:
            - open
            - closed
            - cancelled
          description: TabSync-derived operational status. `closed` means the financial projection has no
            balance due.
        payment_status:
          type: string
          enum:
            - unpaid
            - partially_paid
            - paid
            - partially_refunded
            - refunded
          description: TabSync-derived payment state based on captured principal, refunds and the current
            order total.
        source_version:
          type: string
          minLength: 1
          maxLength: 128
          description: Latest Partner-supplied source revision held by TabSync for this order.
        version:
          type: integer
          minimum: 1
          description: TabSync order version incremented when the held projection changes.
        currency:
          type: string
          pattern: ^[A-Z]{3}$
          description: ISO 4217 currency for all monetary fields on this order.
        amounts:
          $ref: "#/components/schemas/PartnerOrderAmounts"
          description: Current financial projection in minor units.
        lines:
          type: array
          description: Current itemised lines held by TabSync. Empty when the Partner posted a total-only order.
          items:
            $ref: "#/components/schemas/PartnerOrderLine"
        payments:
          type: array
          description: Captured and in-flight settlement records known to TabSync.
          items:
            $ref: "#/components/schemas/PartnerOrderPayment"
        opened_at:
          $ref: "#/components/schemas/DateTime"
          description: Time the order/check was opened in the EPOS.
        settled_at:
          type:
            - string
            - "null"
          format: date-time
          description: Time TabSync first calculated the order as fully settled, or `null` while open/unpaid.
        created_at:
          $ref: "#/components/schemas/DateTime"
          description: Time TabSync first created this order projection.
        updated_at:
          $ref: "#/components/schemas/DateTime"
          description: Time the order projection or settlement state last changed.
    PartnerExternalPaymentCreate:
      type: object
      description: Captured payment taken outside TabSync and reported by the Partner.
      additionalProperties: false
      required:
        - payment_reference
        - payment_method
        - currency
        - principal_amount
      properties:
        payment_reference:
          type: string
          minLength: 1
          maxLength: 128
          description: Partner-side payment identifier used for reconciliation. It is not a secret.
          example: TILL-CARD-1009
        payment_method:
          type: string
          enum:
            - cash
            - card
            - other
          description: Payment method reported by the Partner for settlement taken outside TabSync.
        currency:
          type: string
          pattern: ^[A-Z]{3}$
          description: Must match the order currency.
          example: GBP
        principal_amount:
          type: integer
          format: int64
          minimum: 1
          maximum: 100000000
          description: Amount that settles the order balance, in the order currency's minor unit.
          example: 930
        tip_amount:
          type: integer
          format: int64
          minimum: 0
          maximum: 1000000
          default: 0
          description: Optional gratuity amount recorded for reconciliation. It does not reduce the order
            balance.
        captured_at:
          $ref: "#/components/schemas/DateTime"
          description: Time the external system captured the payment. If omitted, TabSync records the report
            time.
        reported_at:
          $ref: "#/components/schemas/DateTime"
          description: Time the Partner system reported the payment to TabSync. Usually omit this unless
            replaying known historical evidence.
    PartnerCustomer:
      type: object
      description: Partner-managed hospitality business record.
      additionalProperties: false
      required:
        - id
        - name
        - external_reference
        - default_currency
        - status
        - created_at
        - updated_at
      properties:
        id:
          $ref: "#/components/schemas/Uuid"
          description: TabSync Customer ID. Use this as `customer_id` when filtering directory requests.
        name:
          type: string
          description: Customer name held in TabSync.
          example: Cafe Cappuccino
        external_reference:
          type:
            - string
            - "null"
          description: Partner-side Customer reference, when one was supplied in the portal.
          example: CAFE-CAPPUCCINO
        default_currency:
          type: string
          pattern: ^[A-Z]{3}$
          description: Default ISO 4217 currency for this Customer's Locations.
          example: GBP
        status:
          type: string
          enum:
            - active
          description: Directory responses currently expose only active Customers.
        created_at:
          $ref: "#/components/schemas/DateTime"
          description: Time the Customer was created in TabSync.
        updated_at:
          $ref: "#/components/schemas/DateTime"
          description: Time the Customer record last changed in TabSync.
    PartnerLocation:
      type: object
      description: Customer site where orders can be posted and TabSync devices are linked.
      additionalProperties: false
      required:
        - id
        - customer_id
        - name
        - external_reference
        - currency
        - time_zone
        - status
        - offering
        - created_at
        - updated_at
      properties:
        id:
          $ref: "#/components/schemas/Uuid"
          description: TabSync Location ID. Send this as `location_id` when creating or replacing an order.
        customer_id:
          $ref: "#/components/schemas/Uuid"
          description: Customer that owns this Location.
        name:
          type: string
          description: Location name shown in TabSync.
          example: Cafe Cappuccino Guilsborough
        external_reference:
          type:
            - string
            - "null"
          description: Partner-side Location reference, when one was supplied in the portal.
          example: CAFE-GUI
        currency:
          type: string
          pattern: ^[A-Z]{3}$
          description: ISO 4217 currency. Order and payment currency must match this value.
          example: GBP
        time_zone:
          type: string
          description: IANA time zone used for local operational views at this Location.
          example: Europe/London
        status:
          type: string
          enum:
            - active
          description: Directory responses currently expose only active Locations.
        offering:
          description: Active Offering Version pinned to this Location, or `null` until configured.
          oneOf:
            - $ref: "#/components/schemas/PartnerLocationOffering"
            - type: "null"
        created_at:
          $ref: "#/components/schemas/DateTime"
          description: Time the Location was created in TabSync.
        updated_at:
          $ref: "#/components/schemas/DateTime"
          description: Time the Location record last changed in TabSync.
    PartnerArea:
      type: object
      description: Operational Area inside a Location. Orders must be assigned to one Area.
      additionalProperties: false
      required:
        - id
        - customer_id
        - location_id
        - name
        - external_reference
        - status
        - display_order
        - created_at
        - updated_at
      properties:
        id:
          $ref: "#/components/schemas/Uuid"
          description: TabSync Area ID. Send this as `area_id` when creating or replacing an order.
        customer_id:
          $ref: "#/components/schemas/Uuid"
          description: Customer that owns this Area.
        location_id:
          $ref: "#/components/schemas/Uuid"
          description: Location that contains this Area.
        name:
          type: string
          description: Area name shown in TabSync.
          example: Main area
        external_reference:
          type:
            - string
            - "null"
          description: Partner-side Area reference, when one was supplied in the portal.
          example: MAIN
        status:
          type: string
          enum:
            - active
          description: Directory responses currently expose only active Areas.
        display_order:
          type: integer
          minimum: 0
          description: Portal/app display ordering for Areas in the same Location.
          example: 0
        created_at:
          $ref: "#/components/schemas/DateTime"
          description: Time the Area was created in TabSync.
        updated_at:
          $ref: "#/components/schemas/DateTime"
          description: Time the Area record last changed in TabSync.
    PartnerOrderTableInput:
      type: object
      description: Table or check label shown to staff in the TabSync app.
      additionalProperties: false
      required:
        - reference
      properties:
        reference:
          type: string
          minLength: 1
          maxLength: 64
          description: Lookup value used by the app. Keep it stable for the life of the order.
          example: "14"
        display_name:
          type:
            - string
            - "null"
          maxLength: 120
          description: Friendly label shown to staff. If omitted, TabSync can display the reference.
          example: Table 14
    PartnerOrderWriteAmounts:
      type: object
      additionalProperties: false
      description: |
        Every amount is an integer in the order currency's minor unit. The exact
        invariant is `subtotal + tax + service_charge - discount = total`.
      required:
        - subtotal
        - tax
        - total
      properties:
        subtotal:
          type: integer
          format: int64
          minimum: 0
          maximum: 100000000
          description: Net/gross subtotal before tax, service charge and discount according to the Partner's
            model.
        tax:
          type: integer
          format: int64
          minimum: 0
          maximum: 100000000
          description: Tax amount included in the order total.
        service_charge:
          type: integer
          format: int64
          minimum: 0
          maximum: 100000000
          default: 0
          description: Service charge included in the payable total. Defaults to `0`.
        discount:
          type: integer
          format: int64
          minimum: 0
          maximum: 100000000
          default: 0
          description: Discount deducted from the payable total. Defaults to `0`.
        total:
          type: integer
          format: int64
          minimum: 0
          maximum: 100000000
          description: Total principal amount due before TabSync records any payments.
    PartnerOrderLineWrite:
      type: object
      description: Item line supplied by the Partner. Lines are optional, but required for item-selection
        payments.
      additionalProperties: false
      required:
        - external_reference
        - name
        - quantity
        - unit_amount
        - total_amount
        - tax_amount
      properties:
        external_reference:
          type: string
          minLength: 1
          maxLength: 128
          description: Partner-side line reference, unique within this order payload.
        name:
          type: string
          minLength: 1
          maxLength: 255
          description: Item name shown in the TabSync app.
        quantity:
          type: string
          pattern: ^[0-9]{1,7}(\.[0-9]{1,3})?$
          description: Positive decimal quantity with up to three decimal places.
          example: "2"
        unit_amount:
          type: integer
          format: int64
          minimum: 0
          maximum: 100000000
          description: Unit price in minor units.
        total_amount:
          type: integer
          format: int64
          minimum: 0
          maximum: 100000000
          description: Gross line total in minor units. All line totals must add up to the order total.
        tax_amount:
          type: integer
          format: int64
          minimum: 0
          maximum: 100000000
          description: Tax amount attributed to this line.
        category:
          type:
            - string
            - "null"
          maxLength: 120
          description: Optional category shown in the TabSync app.
    DateTime:
      type: string
      format: date-time
      description: RFC 3339 timestamp.
      examples:
        - 2026-08-14T12:24:09.442Z
    PartnerOrderTable:
      type: object
      description: Table/check details currently held against an order.
      additionalProperties: false
      required:
        - reference
        - display_name
      properties:
        reference:
          type: string
          minLength: 1
          maxLength: 64
          description: Partner table lookup value.
        display_name:
          type:
            - string
            - "null"
          maxLength: 120
          description: Friendly table/check label shown in TabSync.
    PartnerOrderAmounts:
      type: object
      additionalProperties: false
      description: Every amount is an integer in the order currency's minor unit; GBP values are pence.
      required:
        - subtotal
        - tax
        - service_charge
        - discount
        - total
        - captured_principal
        - captured_tip
        - refunded_principal
        - refunded_tip
        - balance_due
      properties:
        subtotal:
          type: integer
          format: int64
          minimum: 0
          description: Current order subtotal supplied by the Partner.
        tax:
          type: integer
          format: int64
          minimum: 0
          description: Current tax amount supplied by the Partner.
        service_charge:
          type: integer
          format: int64
          minimum: 0
          description: Current service charge supplied by the Partner.
        discount:
          type: integer
          format: int64
          minimum: 0
          description: Current discount supplied by the Partner.
        total:
          type: integer
          format: int64
          minimum: 0
          description: Current principal amount due before captured payments and refunds.
        captured_principal:
          type: integer
          format: int64
          minimum: 0
          description: Captured amount that reduces the order balance.
        captured_tip:
          type: integer
          format: int64
          minimum: 0
          description: Captured gratuity recorded for reconciliation. It does not reduce the order balance.
        refunded_principal:
          type: integer
          format: int64
          minimum: 0
          description: Principal already refunded.
        refunded_tip:
          type: integer
          format: int64
          minimum: 0
          description: Tip already refunded.
        balance_due:
          type: integer
          format: int64
          minimum: 0
          description: Remaining principal due. `0` means TabSync considers the current order projection paid.
    PartnerOrderLine:
      type: object
      description: Current item line with TabSync's presented paid/remaining state.
      additionalProperties: false
      required:
        - id
        - external_reference
        - name
        - quantity
        - unit_amount
        - total_amount
        - tax_amount
        - paid_amount
        - remaining_amount
        - payment_status
        - category
      properties:
        id:
          $ref: "#/components/schemas/Uuid"
          description: TabSync line ID used in payment allocation responses.
        external_reference:
          type: string
          minLength: 1
          maxLength: 128
          description: Partner-side line reference supplied in the order write.
        name:
          type: string
          minLength: 1
          maxLength: 255
          description: Item name supplied by the Partner.
        quantity:
          type: number
          format: double
          minimum: 0.001
          multipleOf: 0.001
          description: Quantity supplied by the Partner.
        unit_amount:
          type: integer
          format: int64
          minimum: 0
          description: Unit price in minor units.
        total_amount:
          type: integer
          format: int64
          minimum: 0
          description: Gross line total in minor units.
        tax_amount:
          type: integer
          format: int64
          minimum: 0
          description: Tax amount attributed to this line.
        paid_amount:
          type: integer
          format: int64
          minimum: 0
          description: Captured principal presented against this line. Open orders use captured line
            allocations; financially closed orders present every current line as fully paid.
        remaining_amount:
          type: integer
          format: int64
          minimum: 0
          description: Remaining principal presented for this line. This is zero for every current line once
            the order is financially closed.
        payment_status:
          type: string
          enum:
            - unpaid
            - partially_paid
            - paid
          description: Presented line payment state. Once the order is financially closed, every current line
            is presented as paid.
        category:
          type:
            - string
            - "null"
          maxLength: 120
          description: Category supplied by the Partner, or `null`.
    PartnerOrderPayment:
      type: object
      description: Payment or payment attempt known to TabSync.
      additionalProperties: false
      required:
        - id
        - source
        - external_reference
        - method
        - status
        - allocation_type
        - currency
        - amounts
        - trust_level
        - device
        - captured_at
        - reported_at
        - created_at
        - allocations
      properties:
        id:
          $ref: "#/components/schemas/Uuid"
          description: TabSync payment/attempt ID.
        source:
          type: string
          enum:
            - tabsync_device
            - external
          description: "`tabsync_device` was taken through the TabSync app; `external` was reported by the
            Partner."
        external_reference:
          type:
            - string
            - "null"
          maxLength: 128
          description: Partner/provider payment reference, when available.
        method:
          type: string
          enum:
            - cash
            - card
            - other
          description: Payment method reported by the source.
        status:
          type: string
          enum:
            - requires_confirmation
            - captured
            - failed
            - cancelled
          description: Only `captured` payments reduce the order balance.
        allocation_type:
          type: string
          enum:
            - full
            - partial_amount
            - item_selection
          description: How the principal amount was allocated to the order.
        currency:
          type: string
          pattern: ^[A-Z]{3}$
          description: ISO 4217 currency for this payment.
        amounts:
          type: object
          description: Payment amounts in minor units.
          additionalProperties: false
          required:
            - principal
            - tip
            - total
          properties:
            principal:
              type: integer
              format: int64
              minimum: 0
              description: Captured principal that can reduce the order balance.
            tip:
              type: integer
              format: int64
              minimum: 0
              description: Captured gratuity.
            total:
              type: integer
              format: int64
              minimum: 0
              description: Principal plus tip.
        trust_level:
          type:
            - string
            - "null"
          enum:
            - client_reported
            - partner_reported
            - null
          description: Evidence source for the captured result.
        device:
          description: Device that recorded the payment, or `null` for Partner-reported external payments.
          oneOf:
            - type: object
              additionalProperties: false
              required:
                - id
                - name
              properties:
                id:
                  $ref: "#/components/schemas/Uuid"
                  description: TabSync Device ID.
                name:
                  type: string
                  description: Device name shown in the Partner portal.
            - type: "null"
        captured_at:
          type:
            - string
            - "null"
          format: date-time
          description: Time the payment was captured, when known.
        reported_at:
          type:
            - string
            - "null"
          format: date-time
          description: Time the payment result was reported to TabSync, when different from capture time.
        created_at:
          $ref: "#/components/schemas/DateTime"
          description: Time the payment record was created in TabSync.
        allocations:
          type: array
          description: Principal allocation lines used to explain how this payment reduced the balance.
          items:
            $ref: "#/components/schemas/PartnerOrderPaymentAllocation"
    PartnerLocationOffering:
      type: object
      description: Offering Version currently assigned to a Location.
      additionalProperties: false
      required:
        - id
        - name
        - version_id
        - version
      properties:
        id:
          $ref: "#/components/schemas/Uuid"
          description: Offering ID. Use this as `offering_id` when filtering directory requests.
        name:
          type: string
          description: Partner-defined Offering name.
          example: Cafe Connect
        version_id:
          $ref: "#/components/schemas/Uuid"
          description: Exact Offering Version ID assigned to the Location.
        version:
          type: string
          description: Human-readable Offering Version label.
          example: "1.0"
    PartnerOrderPaymentAllocation:
      type: object
      description: Portion of captured principal allocated to either a line or the order remainder.
      additionalProperties: false
      required:
        - target_type
        - order_line_id
        - principal_amount
      properties:
        target_type:
          type: string
          enum:
            - order_line
            - order_remainder
          description: "`order_line` points at a line; `order_remainder` applies to the remaining balance
            without a line."
        order_line_id:
          type:
            - string
            - "null"
          format: uuid
          description: Line ID for `order_line` allocations, otherwise `null`.
        principal_amount:
          type: integer
          format: int64
          minimum: 0
          description: Principal amount allocated to this target.
