openapi: 3.1.0
info:
  title: TDS OpenAPI binding
  version: 0.10.0
  summary: Machine-readable binding of the Transactional Data Specification (TCRP Report 210, 2020).
  description: 'Request/response operations for booking demand-response trips between a requester (broker,
    coordinator tool or provider) and a provider: the core profile (SPEC.md §6–§8) and the transfer profile''s
    tenders and holds (SPEC.md §10, operations marked x-profile: transfer). Events are delivered by signed
    webhook; see spec/asyncapi.yaml. Every mutating call requires an Idempotency-Key header.'
  contact:
    name: tds-openapi maintainers
    url: https://github.com/kovirlabs/tds-openapi
  license:
    name: Apache-2.0
    identifier: Apache-2.0
jsonSchemaDialect: https://json-schema.org/draft/2020-12/schema
servers:
- url: http://localhost:4010
  description: Stateful reference provider (uv run tds-mock)
- url: http://localhost:4011
  description: Static example mock (npm run mock:static)
security:
- oauth2: []
tags:
- name: Trips
  description: Request, confirm, modify, cancel and read trips.
- name: Events
  description: Webhook subscriptions (create, read, change, end) and their signing secrets.
- name: Transfer
  description: 'Transfer profile: tenders and hold requests for protected cross-provider connections.'
- name: Meta
  description: Health and capabilities.
paths:
  /trips:
    post:
      operationId: requestTrip
      summary: Request a trip
      description: Ask a provider to serve a trip. A provider that decides synchronously returns 201 with
        the trip OFFERED (or CONFIRMED, skipping the offer step). One that decides later returns 202 with
        the trip REQUESTED and reports the decision by event. Both are conformant. A synchronous refusal
        is a 422 whose `code` says why; the trip is then REJECTED.
      tags:
      - Trips
      security:
      - oauth2:
        - trips:write
      parameters:
      - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/trip-request'
            examples:
              single_trip:
                summary: Appointment trip, Ann Arbor to Troy
                value:
                  trip_id: tr-20261103-0001
                  requester_id: throughride-demo
                  provider_id: aaata-a-ride
                  rider_ref:
                    rider_ref_id: r-7f3a
                    display_name: Dana K.
                    language: en
                    eligibility:
                    - program: ADA_PARATRANSIT
                      attested_by: aaata
                      valid_until: '2027-06-30'
                      verification_method: AGENCY_DETERMINATION
                      verified_at: '2025-07-01'
                  party:
                    companions: 0
                    attendants: 1
                    service_animals: 0
                  legs:
                  - leg_id: leg-1
                    pickup:
                      lat: 42.2808
                      lon: -83.743
                      name: Blake Transit Center
                      gtfs_stop_id: BTC
                    dropoff:
                      lat: 42.6064
                      lon: -83.1498
                      name: Corewell Health Troy
                      access_notes: Main entrance, Dequindre Rd side
                    dropoff_window:
                      earliest: '2026-11-03T10:15:00-05:00'
                      latest: '2026-11-03T10:45:00-05:00'
                    service_requirements:
                    - WHEELCHAIR_LIFT
                  fare_funding:
                    fare:
                      amount: '0.00'
                      currency: USD
                    funding_source: SPONSOR
                    sponsor_ref: jfs-washtenaw
                  notes_to_driver: Call on arrival.
              cancellation:
                summary: Local pickup-anchored trip
                value:
                  trip_id: tr-20261105-0007
                  requester_id: throughride-demo
                  provider_id: aaata-a-ride
                  rider_ref:
                    rider_ref_id: r-91c2
                    display_name: Luis
                    contact:
                      phone: '+17345550123'
                    contact_consent: true
                    language: es
                  legs:
                  - leg_id: leg-1
                    pickup:
                      lat: 42.2459
                      lon: -83.6157
                      name: Ypsilanti Transit Center
                      gtfs_stop_id: YTC
                    dropoff:
                      lat: 42.277
                      lon: -83.7382
                      name: Ann Arbor District Library, Downtown
                    pickup_window:
                      earliest: '2026-11-05T13:00:00-05:00'
                      preferred: '2026-11-05T13:15:00-05:00'
                      latest: '2026-11-05T13:30:00-05:00'
                  fare_funding:
                    fare:
                      amount: '2.50'
                      currency: USD
                    funding_source: RIDER
              eligibility_attestation:
                summary: Senior ride with an AGE_60_PLUS fact verified by digital ID
                value:
                  trip_id: tr-20261112-0031
                  requester_id: throughride-demo
                  provider_id: aaata-a-ride
                  rider_ref:
                    rider_ref_id: r-c40e
                    display_name: Ruth
                    language: en
                    eligibility:
                    - program: AGE_60_PLUS
                      attested_by: throughride-demo
                      verification_method: DIGITAL_ID
                      verified_at: '2026-10-15'
                      verifier_ref: state-mdl-issuer
                  legs:
                  - leg_id: leg-1
                    pickup:
                      lat: 42.167
                      lon: -83.7816
                      name: Saline District Library
                    dropoff:
                      lat: 42.2808
                      lon: -83.743
                      name: Blake Transit Center
                      gtfs_stop_id: BTC
                    pickup_window:
                      earliest: '2026-11-12T09:30:00-05:00'
                      latest: '2026-11-12T10:00:00-05:00'
                  fare_funding:
                    fare:
                      amount: '0.00'
                      currency: USD
                    funding_source: SECTION_5310
              rider_category_facts:
                summary: Senior and disabled county resident, attested as facts for reporting
                value:
                  trip_id: tr-20261118-0107
                  requester_id: throughride-demo
                  provider_id: county-connector
                  rider_ref:
                    rider_ref_id: r-5b19
                    display_name: Walter
                    language: en
                    eligibility:
                    - program: AGE_65_PLUS
                      attested_by: throughride-demo
                      verification_method: STAFF_VERIFIED
                      verified_at: '2026-09-02'
                    - program: DISABILITY
                      attested_by: throughride-demo
                      verification_method: STAFF_VERIFIED
                      verified_at: '2026-09-02'
                      valid_until: '2027-09-01'
                    - program: RESIDENT_US_26161
                      attested_by: throughride-demo
                      verification_method: STAFF_VERIFIED
                      verified_at: '2026-09-02'
                      valid_until: '2027-09-01'
                  legs:
                  - leg_id: leg-1
                    pickup:
                      lat: 42.2411
                      lon: -83.613
                      name: Ypsilanti Transit Center
                    dropoff:
                      lat: 42.2808
                      lon: -83.743
                      name: Blake Transit Center
                      gtfs_stop_id: BTC
                    pickup_window:
                      earliest: '2026-11-18T13:00:00-05:00'
                      latest: '2026-11-18T13:30:00-05:00'
                    service_requirements:
                    - WHEELCHAIR_LIFT
                  fare_funding:
                    fare:
                      amount: '0.00'
                      currency: USD
                    funding_source: SECTION_5310
              booking_window_too_early:
                summary: Trip 17 days ahead, beyond the provider's 14-day booking window
                value:
                  trip_id: tr-20261118-0112
                  requester_id: throughride-demo
                  provider_id: county-connector
                  rider_ref:
                    rider_ref_id: r-9d02
                    language: en
                  legs:
                  - leg_id: leg-1
                    pickup:
                      lat: 42.2411
                      lon: -83.613
                      name: Ypsilanti Transit Center
                    dropoff:
                      lat: 42.2808
                      lon: -83.743
                      name: Blake Transit Center
                      gtfs_stop_id: BTC
                    pickup_window:
                      earliest: '2026-11-18T13:00:00-05:00'
                      latest: '2026-11-18T13:30:00-05:00'
      responses:
        '201':
          description: Decided synchronously; the trip is OFFERED or CONFIRMED.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/trip'
              examples:
                single_trip:
                  summary: Offered with a scheduled pickup
                  value:
                    trip_id: tr-20261103-0001
                    provider_trip_id: AR-558812
                    requester_id: throughride-demo
                    provider_id: aaata-a-ride
                    status: OFFERED
                    rider_ref:
                      rider_ref_id: r-7f3a
                      display_name: Dana K.
                      language: en
                      eligibility:
                      - program: ADA_PARATRANSIT
                        attested_by: aaata
                        valid_until: '2027-06-30'
                        verification_method: AGENCY_DETERMINATION
                        verified_at: '2025-07-01'
                    party:
                      companions: 0
                      attendants: 1
                      service_animals: 0
                    legs:
                    - leg_id: leg-1
                      pickup:
                        lat: 42.2808
                        lon: -83.743
                        name: Blake Transit Center
                        gtfs_stop_id: BTC
                      dropoff:
                        lat: 42.6064
                        lon: -83.1498
                        name: Corewell Health Troy
                        access_notes: Main entrance, Dequindre Rd side
                      dropoff_window:
                        earliest: '2026-11-03T10:15:00-05:00'
                        latest: '2026-11-03T10:45:00-05:00'
                      scheduled_pickup: '2026-11-03T09:20:00-05:00'
                      scheduled_dropoff: '2026-11-03T10:30:00-05:00'
                      service_requirements:
                      - WHEELCHAIR_LIFT
                    fare_funding:
                      fare:
                        amount: '0.00'
                        currency: USD
                      funding_source: SPONSOR
                      sponsor_ref: jfs-washtenaw
                    notes_to_driver: Call on arrival.
                    created_at: '2026-10-30T14:02:11-04:00'
                    updated_at: '2026-10-30T14:02:12-04:00'
                eligibility_attestation:
                  summary: Offered; the provider accepts DIGITAL_ID attestations
                  value:
                    trip_id: tr-20261112-0031
                    provider_trip_id: AR-561207
                    requester_id: throughride-demo
                    provider_id: aaata-a-ride
                    status: OFFERED
                    rider_ref:
                      rider_ref_id: r-c40e
                      display_name: Ruth
                      language: en
                      eligibility:
                      - program: AGE_60_PLUS
                        attested_by: throughride-demo
                        verification_method: DIGITAL_ID
                        verified_at: '2026-10-15'
                        verifier_ref: state-mdl-issuer
                    legs:
                    - leg_id: leg-1
                      pickup:
                        lat: 42.167
                        lon: -83.7816
                        name: Saline District Library
                      dropoff:
                        lat: 42.2808
                        lon: -83.743
                        name: Blake Transit Center
                        gtfs_stop_id: BTC
                      pickup_window:
                        earliest: '2026-11-12T09:30:00-05:00'
                        latest: '2026-11-12T10:00:00-05:00'
                      scheduled_pickup: '2026-11-12T09:45:00-05:00'
                      scheduled_dropoff: '2026-11-12T10:15:00-05:00'
                    fare_funding:
                      fare:
                        amount: '0.00'
                        currency: USD
                      funding_source: SECTION_5310
                    created_at: '2026-11-05T11:20:03-05:00'
                    updated_at: '2026-11-05T11:20:04-05:00'
                rider_category_facts:
                  summary: Offered; attestations kept as sent
                  value:
                    trip_id: tr-20261118-0107
                    provider_trip_id: CC-118804
                    requester_id: throughride-demo
                    provider_id: county-connector
                    status: OFFERED
                    rider_ref:
                      rider_ref_id: r-5b19
                      display_name: Walter
                      language: en
                      eligibility:
                      - program: AGE_65_PLUS
                        attested_by: throughride-demo
                        verification_method: STAFF_VERIFIED
                        verified_at: '2026-09-02'
                      - program: DISABILITY
                        attested_by: throughride-demo
                        verification_method: STAFF_VERIFIED
                        verified_at: '2026-09-02'
                        valid_until: '2027-09-01'
                      - program: RESIDENT_US_26161
                        attested_by: throughride-demo
                        verification_method: STAFF_VERIFIED
                        verified_at: '2026-09-02'
                        valid_until: '2027-09-01'
                    legs:
                    - leg_id: leg-1
                      pickup:
                        lat: 42.2411
                        lon: -83.613
                        name: Ypsilanti Transit Center
                      dropoff:
                        lat: 42.2808
                        lon: -83.743
                        name: Blake Transit Center
                        gtfs_stop_id: BTC
                      pickup_window:
                        earliest: '2026-11-18T13:00:00-05:00'
                        latest: '2026-11-18T13:30:00-05:00'
                      service_requirements:
                      - WHEELCHAIR_LIFT
                      scheduled_pickup: '2026-11-18T13:15:00-05:00'
                      scheduled_dropoff: '2026-11-18T13:50:00-05:00'
                    fare_funding:
                      fare:
                        amount: '0.00'
                        currency: USD
                      funding_source: SECTION_5310
                    created_at: '2026-11-10T09:05:12-05:00'
                    updated_at: '2026-11-10T09:05:13-05:00'
        '202':
          description: Accepted for an asynchronous decision; the trip is REQUESTED.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/trip'
              examples:
                cancellation:
                  summary: Provider will decide later
                  value:
                    trip_id: tr-20261105-0007
                    requester_id: throughride-demo
                    provider_id: aaata-a-ride
                    rider_ref:
                      rider_ref_id: r-91c2
                      display_name: Luis
                      contact:
                        phone: '+17345550123'
                      contact_consent: true
                      language: es
                    legs:
                    - leg_id: leg-1
                      pickup:
                        lat: 42.2459
                        lon: -83.6157
                        name: Ypsilanti Transit Center
                        gtfs_stop_id: YTC
                      dropoff:
                        lat: 42.277
                        lon: -83.7382
                        name: Ann Arbor District Library, Downtown
                      pickup_window:
                        earliest: '2026-11-05T13:00:00-05:00'
                        preferred: '2026-11-05T13:15:00-05:00'
                        latest: '2026-11-05T13:30:00-05:00'
                    fare_funding:
                      fare:
                        amount: '2.50'
                        currency: USD
                      funding_source: RIDER
                    status: REQUESTED
                    created_at: '2026-11-02T17:40:00-05:00'
                    updated_at: '2026-11-02T17:40:00-05:00'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '409':
          description: Idempotency-Key reused with a different body (code IDEMPOTENCY_KEY_REUSED), or
            this requester already has a trip with this trip_id under another key (code TRIP_ID_IN_USE).
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/error'
              examples:
                idempotency_key_reused:
                  value:
                    type: about:blank
                    title: Idempotency key reused
                    status: 409
                    detail: This Idempotency-Key was used earlier with a different request body.
                    code: IDEMPOTENCY_KEY_REUSED
        '422':
          description: The provider refuses the trip; `code` says why and the trip is REJECTED.
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/error'
              examples:
                no_capacity:
                  value:
                    type: about:blank
                    title: No capacity
                    status: 422
                    detail: No vehicle with a wheelchair lift is available in the requested window.
                    code: NO_CAPACITY
                eligibility_not_accepted:
                  summary: The provider does not accept the attestation's verification method
                  value:
                    type: about:blank
                    title: Eligibility attestation not accepted
                    status: 422
                    detail: This provider accepts AGE_60_PLUS attestations verified by STAFF_VERIFIED
                      or AGENCY_DETERMINATION only.
                    code: ELIGIBILITY_NOT_ACCEPTED
                outside_booking_window:
                  summary: Too early; bookable_from says when to submit again
                  value:
                    type: about:blank
                    title: Outside booking window
                    status: 422
                    detail: Trips are booked at most 14 days ahead.
                    code: OUTSIDE_BOOKING_WINDOW
                    bookable_from: '2026-11-04T13:00:00-05:00'
                too_little_notice:
                  summary: Too late for the provider's minimum notice; no retry hint
                  value:
                    type: about:blank
                    title: Outside booking window
                    status: 422
                    detail: Trips need at least 1 day of notice.
                    code: OUTSIDE_BOOKING_WINDOW
        '429':
          description: 'Back-pressure (SPEC.md §7.2): too many requests from this client (code RATE_LIMITED).
            The trip was not created. Retry the same request with the same Idempotency-Key after Retry-After
            / retry_after_s.'
          headers:
            Retry-After:
              $ref: '#/components/headers/RetryAfter'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/error'
              examples:
                rate_limited:
                  summary: Over the provider's per-client limit; retry in 20 s
                  value:
                    type: about:blank
                    title: Too many requests
                    status: 429
                    detail: More than 60 requests in a minute from this client. Retry the same request
                      after the wait.
                    code: RATE_LIMITED
                    retry_after_s: 20
        '503':
          description: 'Back-pressure (SPEC.md §7.2): the provider cannot take requests for now (code
            TEMPORARILY_UNAVAILABLE). The trip was not created. Retry the same request with the same Idempotency-Key
            after Retry-After / retry_after_s.'
          headers:
            Retry-After:
              $ref: '#/components/headers/RetryAfter'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/error'
              examples:
                temporarily_unavailable:
                  summary: Scheduling system busy (nightly optimisation run); retry in 120 s
                  value:
                    type: about:blank
                    title: Temporarily unavailable
                    status: 503
                    detail: The scheduling system is running its nightly optimisation. Retry the same
                      request after the wait.
                    code: TEMPORARILY_UNAVAILABLE
                    retry_after_s: 120
  /trips/{trip_id}:
    parameters:
    - $ref: '#/components/parameters/TripId'
    get:
      operationId: getTrip
      summary: Get a trip
      description: Current state of a trip, including scheduled times and vehicle once known.
      tags:
      - Trips
      security:
      - oauth2:
        - trips:read
      responses:
        '200':
          description: The trip.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/trip'
              examples:
                single_trip:
                  summary: Scheduled, vehicle assigned
                  value:
                    trip_id: tr-20261103-0001
                    provider_trip_id: AR-558812
                    requester_id: throughride-demo
                    provider_id: aaata-a-ride
                    status: SCHEDULED
                    rider_ref:
                      rider_ref_id: r-7f3a
                      display_name: Dana K.
                      language: en
                      eligibility:
                      - program: ADA_PARATRANSIT
                        attested_by: aaata
                        valid_until: '2027-06-30'
                        verification_method: AGENCY_DETERMINATION
                        verified_at: '2025-07-01'
                    party:
                      companions: 0
                      attendants: 1
                      service_animals: 0
                    legs:
                    - leg_id: leg-1
                      pickup:
                        lat: 42.2808
                        lon: -83.743
                        name: Blake Transit Center
                        gtfs_stop_id: BTC
                      dropoff:
                        lat: 42.6064
                        lon: -83.1498
                        name: Corewell Health Troy
                        access_notes: Main entrance, Dequindre Rd side
                      dropoff_window:
                        earliest: '2026-11-03T10:15:00-05:00'
                        latest: '2026-11-03T10:45:00-05:00'
                      scheduled_pickup: '2026-11-03T09:20:00-05:00'
                      scheduled_dropoff: '2026-11-03T10:30:00-05:00'
                      service_requirements:
                      - WHEELCHAIR_LIFT
                      vehicle_ref:
                        vehicle_id: veh-214
                        description: 'White Ford Transit, #214'
                        plate: ABC1234
                    fare_funding:
                      fare:
                        amount: '0.00'
                        currency: USD
                      funding_source: SPONSOR
                      sponsor_ref: jfs-washtenaw
                    notes_to_driver: Call on arrival.
                    created_at: '2026-10-30T14:02:11-04:00'
                    updated_at: '2026-11-02T16:40:05-05:00'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
    patch:
      operationId: modifyTrip
      summary: Modify a trip
      description: Change windows, party or driver notes. The provider may accept (200, status unchanged
        or CONFIRMED), re-offer (200, status OFFERED), or decide later (202). If it refuses, the trip
        keeps its previous terms and the response is 422.
      tags:
      - Trips
      security:
      - oauth2:
        - trips:write
      parameters:
      - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/trip-modification'
      responses:
        '200':
          description: Change applied or re-offered.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/trip'
        '202':
          description: Change accepted for an asynchronous decision.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/trip'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          description: The trip is in a terminal state (code INVALID_STATE) or the Idempotency-Key was
            reused.
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/error'
        '422':
          description: The provider refuses the change; previous terms stand.
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/error'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
  /trips/{trip_id}/confirm:
    parameters:
    - $ref: '#/components/parameters/TripId'
    post:
      operationId: confirmTrip
      summary: Confirm an offered trip
      description: Accept the provider's offer. Only valid while the trip is OFFERED.
      tags:
      - Trips
      security:
      - oauth2:
        - trips:write
      parameters:
      - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '200':
          description: The trip is CONFIRMED.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/trip'
              examples:
                single_trip:
                  value:
                    trip_id: tr-20261103-0001
                    provider_trip_id: AR-558812
                    requester_id: throughride-demo
                    provider_id: aaata-a-ride
                    status: CONFIRMED
                    rider_ref:
                      rider_ref_id: r-7f3a
                      display_name: Dana K.
                      language: en
                      eligibility:
                      - program: ADA_PARATRANSIT
                        attested_by: aaata
                        valid_until: '2027-06-30'
                        verification_method: AGENCY_DETERMINATION
                        verified_at: '2025-07-01'
                    party:
                      companions: 0
                      attendants: 1
                      service_animals: 0
                    legs:
                    - leg_id: leg-1
                      pickup:
                        lat: 42.2808
                        lon: -83.743
                        name: Blake Transit Center
                        gtfs_stop_id: BTC
                      dropoff:
                        lat: 42.6064
                        lon: -83.1498
                        name: Corewell Health Troy
                        access_notes: Main entrance, Dequindre Rd side
                      dropoff_window:
                        earliest: '2026-11-03T10:15:00-05:00'
                        latest: '2026-11-03T10:45:00-05:00'
                      scheduled_pickup: '2026-11-03T09:20:00-05:00'
                      scheduled_dropoff: '2026-11-03T10:30:00-05:00'
                      service_requirements:
                      - WHEELCHAIR_LIFT
                    fare_funding:
                      fare:
                        amount: '0.00'
                        currency: USD
                      funding_source: SPONSOR
                      sponsor_ref: jfs-washtenaw
                    notes_to_driver: Call on arrival.
                    created_at: '2026-10-30T14:02:11-04:00'
                    updated_at: '2026-10-30T14:05:40-04:00'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          description: The trip is not OFFERED (code INVALID_STATE), e.g. the offer EXPIRED.
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/error'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
  /trips/{trip_id}/cancel:
    parameters:
    - $ref: '#/components/parameters/TripId'
    post:
      operationId: cancelTrip
      summary: Cancel a trip
      description: Cancel a trip that has not reached a terminal state. The response carries the trip
        with status CANCELLED and a cancellation record.
      tags:
      - Trips
      security:
      - oauth2:
        - trips:write
      parameters:
      - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/cancel-request'
            examples:
              cancellation:
                value:
                  reason: RIDER_REQUEST
              missed_connection:
                summary: Re-protecting a leg after a missed connection
                value:
                  reason: MISSED_CONNECTION
      responses:
        '200':
          description: The trip is CANCELLED.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/trip'
              examples:
                cancellation:
                  value:
                    trip_id: tr-20261105-0007
                    requester_id: throughride-demo
                    provider_id: aaata-a-ride
                    rider_ref:
                      rider_ref_id: r-91c2
                      display_name: Luis
                      contact:
                        phone: '+17345550123'
                      contact_consent: true
                      language: es
                    legs:
                    - leg_id: leg-1
                      pickup:
                        lat: 42.2459
                        lon: -83.6157
                        name: Ypsilanti Transit Center
                        gtfs_stop_id: YTC
                      dropoff:
                        lat: 42.277
                        lon: -83.7382
                        name: Ann Arbor District Library, Downtown
                      pickup_window:
                        earliest: '2026-11-05T13:00:00-05:00'
                        preferred: '2026-11-05T13:15:00-05:00'
                        latest: '2026-11-05T13:30:00-05:00'
                    fare_funding:
                      fare:
                        amount: '2.50'
                        currency: USD
                      funding_source: RIDER
                    status: CANCELLED
                    created_at: '2026-11-02T17:40:00-05:00'
                    updated_at: '2026-11-02T18:05:00-05:00'
                    cancellation:
                      reason: RIDER_REQUEST
                      cancelled_by: REQUESTER
                      cancelled_at: '2026-11-02T18:05:00-05:00'
                missed_connection:
                  value:
                    trip_id: tr-rp-b
                    requester_id: throughride-demo
                    provider_id: oakland-go
                    rider_ref:
                      rider_ref_id: r-oakl-55e1
                      display_name: Dana K.
                      language: en
                    legs:
                    - leg_id: leg-1
                      pickup:
                        lat: 42.4802
                        lon: -83.4755
                        name: County-line transfer hub
                        access_notes: Covered bay 2
                      dropoff:
                        lat: 42.6064
                        lon: -83.1498
                        name: Corewell Health Troy
                        access_notes: Main entrance
                      pickup_window:
                        earliest: '2026-11-10T09:45:00-05:00'
                        latest: '2026-11-10T10:00:00-05:00'
                      service_requirements:
                      - WHEELCHAIR_LIFT
                      scheduled_pickup: '2026-11-10T10:00:00-05:00'
                      vehicle_ref:
                        vehicle_id: oakland-go-veh-1
                        description: Mock vehicle
                    status: CANCELLED
                    created_at: '2026-11-10T07:55:00-05:00'
                    updated_at: '2026-11-10T09:10:00-05:00'
                    provider_trip_id: oakland-go-0007
                    cancellation:
                      reason: MISSED_CONNECTION
                      cancelled_by: REQUESTER
                      cancelled_at: '2026-11-10T09:10:00-05:00'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          description: The trip is already in a terminal state (code INVALID_STATE).
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/error'
              examples:
                already_cancelled:
                  value:
                    type: about:blank
                    title: Invalid state
                    status: 409
                    detail: Trip tr-20261105-0007 is already CANCELLED.
                    instance: /trips/tr-20261105-0007/cancel
                    code: INVALID_STATE
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
  /trips/{trip_id}/hold:
    parameters:
    - $ref: '#/components/parameters/TripId'
    post:
      operationId: requestHold
      summary: Ask the outbound vehicle to hold or retime
      description: 'Sent to the outbound provider when the inbound leg''s ETA threatens the connection
        (SPEC.md §10.5). Answered at once (201: GRANTED, PARTIAL or DENIED) or later (202 PENDING, then
        a HOLD_RESPONSE event); unanswered by respond_by, the hold is EXPIRED. GRANTED and PARTIAL also
        update the trip''s scheduled_pickup and emit TRIP_SCHEDULED.'
      x-profile: transfer
      tags:
      - Transfer
      security:
      - oauth2:
        - trips:write
      parameters:
      - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/hold-request'
            examples:
              late_feeder:
                summary: Hold for a feeder running 15 minutes late
                value:
                  hold_id: hd-hold-1
                  interchange_id: ix-hold
                  action: HOLD
                  requested_until: '2026-11-10T09:50:00-05:00'
                  respond_by: '2026-11-10T09:11:00-05:00'
                  inbound_eta:
                    target: DROPOFF
                    earliest: '2026-11-10T09:42:00-05:00'
                    likely: '2026-11-10T09:45:00-05:00'
                    latest: '2026-11-10T09:57:30-05:00'
                    confidence: 0.7
                    computed_at: '2026-11-10T09:05:00-05:00'
                    source: VEHICLE_POSITION
                    delay_cause: TRAFFIC
              long_delay:
                summary: Hold for a feeder running 40 minutes late
                value:
                  hold_id: hd-rp-1
                  interchange_id: ix-rp
                  action: HOLD
                  requested_until: '2026-11-10T10:15:00-05:00'
                  respond_by: '2026-11-10T09:06:00-05:00'
                  inbound_eta:
                    target: DROPOFF
                    earliest: '2026-11-10T10:07:00-05:00'
                    likely: '2026-11-10T10:10:00-05:00'
                    latest: '2026-11-10T10:35:00-05:00'
                    confidence: 0.7
                    computed_at: '2026-11-10T09:00:00-05:00'
                    source: VEHICLE_POSITION
                    delay_cause: VEHICLE
              retime:
                summary: Ask the onward vehicle to move its pickup
                value:
                  hold_id: hd-rp-2
                  interchange_id: ix-rp
                  action: RETIME
                  requested_until: '2026-11-10T10:15:00-05:00'
                  respond_by: '2026-11-10T09:08:00-05:00'
      responses:
        '201':
          description: Answered at once.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/hold'
              examples:
                granted:
                  summary: Granted; onward pickup moved
                  value:
                    hold_id: hd-hold-1
                    trip_id: tr-hold-b
                    interchange_id: ix-hold
                    action: HOLD
                    requested_until: '2026-11-10T09:50:00-05:00'
                    respond_by: '2026-11-10T09:11:00-05:00'
                    status: GRANTED
                    created_at: '2026-11-10T09:06:00-05:00'
                    scheduled_pickup: '2026-11-10T09:50:00-05:00'
                    held_until: '2026-11-10T09:50:00-05:00'
                    responded_at: '2026-11-10T09:06:00-05:00'
                counter_offer:
                  summary: Can't wait that long; counter-offers a later pickup (PARTIAL)
                  value:
                    hold_id: hd-rp-1
                    trip_id: tr-rp-b
                    interchange_id: ix-rp
                    action: HOLD
                    requested_until: '2026-11-10T10:15:00-05:00'
                    respond_by: '2026-11-10T09:06:00-05:00'
                    status: PARTIAL
                    created_at: '2026-11-10T09:01:00-05:00'
                    reason: DOWNSTREAM_COMMITMENT
                    scheduled_pickup: '2026-11-10T10:00:00-05:00'
                    held_until: '2026-11-10T10:00:00-05:00'
                    responded_at: '2026-11-10T09:01:00-05:00'
        '202':
          description: Received; the answer follows as a HOLD_RESPONSE event.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/hold'
              examples:
                pending:
                  summary: Not answered yet
                  value:
                    hold_id: hd-rp-2
                    trip_id: tr-rp-b
                    interchange_id: ix-rp
                    action: RETIME
                    requested_until: '2026-11-10T10:15:00-05:00'
                    respond_by: '2026-11-10T09:08:00-05:00'
                    status: PENDING
                    created_at: '2026-11-10T09:03:00-05:00'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          description: The trip is not the outbound leg of that interchange or has already departed (code
            INVALID_STATE), or hold_id is in use (TRIP_ID_IN_USE).
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/error'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
  /trips/{trip_id}/holds/{hold_id}:
    parameters:
    - $ref: '#/components/parameters/TripId'
    - $ref: '#/components/parameters/HoldId'
    get:
      operationId: getHold
      summary: Get a hold request
      description: Current state of a hold request.
      x-profile: transfer
      tags:
      - Transfer
      security:
      - oauth2:
        - trips:read
      responses:
        '200':
          description: The hold.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/hold'
              examples:
                granted:
                  summary: A granted hold
                  value:
                    hold_id: hd-hold-1
                    trip_id: tr-hold-b
                    interchange_id: ix-hold
                    action: HOLD
                    requested_until: '2026-11-10T09:50:00-05:00'
                    respond_by: '2026-11-10T09:11:00-05:00'
                    status: GRANTED
                    created_at: '2026-11-10T09:06:00-05:00'
                    scheduled_pickup: '2026-11-10T09:50:00-05:00'
                    held_until: '2026-11-10T09:50:00-05:00'
                    responded_at: '2026-11-10T09:06:00-05:00'
                expired:
                  summary: Unanswered by respond_by (the broker raises HOLD_UNANSWERED)
                  value:
                    hold_id: hd-rp-2
                    trip_id: tr-rp-b
                    interchange_id: ix-rp
                    action: RETIME
                    requested_until: '2026-11-10T10:15:00-05:00'
                    respond_by: '2026-11-10T09:08:00-05:00'
                    status: EXPIRED
                    created_at: '2026-11-10T09:03:00-05:00'
                    responded_at: '2026-11-10T09:09:00-05:00'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
  /tenders:
    post:
      operationId: createTender
      summary: Tender a leg
      description: Offer this provider one leg of a linked journey with the interchange terms and a deadline
        (SPEC.md §10.2). A provider that decides at once returns 201 with the tender ACCEPTED (the trip
        now exists, CONFIRMED, under trip.trip_id) or REJECTED. One that decides later returns 202 OFFERED
        and reports the answer in a TENDER_RESPONSE event; unanswered by respond_by, the tender becomes
        EXPIRED. Both are conformant.
      x-profile: transfer
      tags:
      - Transfer
      security:
      - oauth2:
        - tenders:write
      parameters:
      - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/tender-request'
            examples:
              feeder_leg:
                summary: Tender the feeder leg of a protected connection
                value:
                  tender_id: tn-tpt-a
                  trip:
                    trip_id: tr-tpt-a
                    requester_id: throughride-demo
                    provider_id: aaata-a-ride
                    rider_ref:
                      rider_ref_id: r-aaat-55e1
                      display_name: Dana K.
                      language: en
                    legs:
                    - leg_id: leg-1
                      pickup:
                        lat: 42.2459
                        lon: -83.6157
                        name: Rider's home (Ypsilanti)
                        access_notes: Side door, ramp
                      dropoff:
                        lat: 42.4802
                        lon: -83.4755
                        name: County-line transfer hub
                        access_notes: Covered bay 2
                      pickup_window:
                        earliest: '2026-11-10T08:50:00-05:00'
                        latest: '2026-11-10T09:05:00-05:00'
                      service_requirements:
                      - WHEELCHAIR_LIFT
                  interchange:
                    interchange_id: ix-tpt
                    from:
                      provider_id: aaata-a-ride
                      trip_id: tr-tpt-a
                      leg_id: leg-1
                    to:
                      provider_id: oakland-go
                      trip_id: tr-tpt-b
                      leg_id: leg-1
                    transfer_location:
                      lat: 42.4802
                      lon: -83.4755
                      name: County-line transfer hub
                      access_notes: Covered bay 2
                    min_connection_s: 300
                    max_hold_s: 900
                    auto_hold_s: 300
                    accessibility_buffer_s: 300
                    protection: PROTECTED
                    supervision_required: false
                    heartbeat_s: 300
                  respond_by: '2026-11-10T08:10:00-05:00'
              onward_leg:
                summary: Tender the onward leg
                value:
                  tender_id: tn-tpt-b
                  trip:
                    trip_id: tr-tpt-b
                    requester_id: throughride-demo
                    provider_id: oakland-go
                    rider_ref:
                      rider_ref_id: r-oakl-55e1
                      display_name: Dana K.
                      language: en
                    legs:
                    - leg_id: leg-1
                      pickup:
                        lat: 42.4802
                        lon: -83.4755
                        name: County-line transfer hub
                        access_notes: Covered bay 2
                      dropoff:
                        lat: 42.6064
                        lon: -83.1498
                        name: Corewell Health Troy
                        access_notes: Main entrance
                      pickup_window:
                        earliest: '2026-11-10T09:45:00-05:00'
                        latest: '2026-11-10T10:00:00-05:00'
                      service_requirements:
                      - WHEELCHAIR_LIFT
                  interchange:
                    interchange_id: ix-tpt
                    from:
                      provider_id: aaata-a-ride
                      trip_id: tr-tpt-a
                      leg_id: leg-1
                    to:
                      provider_id: oakland-go
                      trip_id: tr-tpt-b
                      leg_id: leg-1
                    transfer_location:
                      lat: 42.4802
                      lon: -83.4755
                      name: County-line transfer hub
                      access_notes: Covered bay 2
                    min_connection_s: 300
                    max_hold_s: 900
                    auto_hold_s: 300
                    accessibility_buffer_s: 300
                    protection: PROTECTED
                    supervision_required: false
                    heartbeat_s: 300
                  respond_by: '2026-11-10T08:10:00-05:00'
              short_deadline:
                summary: Tender with a 5-minute deadline (it will expire)
                value:
                  tender_id: tn-rt-b
                  trip:
                    trip_id: tr-rt-b
                    requester_id: throughride-demo
                    provider_id: oakland-go
                    rider_ref:
                      rider_ref_id: r-oakl-55e1
                      display_name: Dana K.
                      language: en
                    legs:
                    - leg_id: leg-1
                      pickup:
                        lat: 42.4802
                        lon: -83.4755
                        name: County-line transfer hub
                        access_notes: Covered bay 2
                      dropoff:
                        lat: 42.6064
                        lon: -83.1498
                        name: Corewell Health Troy
                        access_notes: Main entrance
                      pickup_window:
                        earliest: '2026-11-10T09:45:00-05:00'
                        latest: '2026-11-10T10:00:00-05:00'
                      service_requirements:
                      - WHEELCHAIR_LIFT
                  interchange:
                    interchange_id: ix-rt
                    from:
                      provider_id: aaata-a-ride
                      trip_id: tr-rt-a
                      leg_id: leg-1
                    to:
                      provider_id: oakland-go
                      trip_id: tr-rt-b
                      leg_id: leg-1
                    transfer_location:
                      lat: 42.4802
                      lon: -83.4755
                      name: County-line transfer hub
                      access_notes: Covered bay 2
                    min_connection_s: 300
                    max_hold_s: 900
                    auto_hold_s: 300
                    accessibility_buffer_s: 300
                    protection: PROTECTED
                    supervision_required: false
                    heartbeat_s: 300
                  respond_by: '2026-11-10T08:00:00-05:00'
              retender:
                summary: Re-tender to the fallback provider after expiry
                value:
                  tender_id: tn-rt-b2
                  trip:
                    trip_id: tr-rt-b2
                    requester_id: throughride-demo
                    provider_id: county-connector
                    rider_ref:
                      rider_ref_id: r-coun-55e1
                      display_name: Dana K.
                      language: en
                    legs:
                    - leg_id: leg-1
                      pickup:
                        lat: 42.4802
                        lon: -83.4755
                        name: County-line transfer hub
                        access_notes: Covered bay 2
                      dropoff:
                        lat: 42.6064
                        lon: -83.1498
                        name: Corewell Health Troy
                        access_notes: Main entrance
                      pickup_window:
                        earliest: '2026-11-10T09:45:00-05:00'
                        latest: '2026-11-10T10:00:00-05:00'
                      service_requirements:
                      - WHEELCHAIR_LIFT
                  interchange:
                    interchange_id: ix-rt-2
                    from:
                      provider_id: aaata-a-ride
                      trip_id: tr-rt-a
                      leg_id: leg-1
                    to:
                      provider_id: county-connector
                      trip_id: tr-rt-b2
                      leg_id: leg-1
                    transfer_location:
                      lat: 42.4802
                      lon: -83.4755
                      name: County-line transfer hub
                      access_notes: Covered bay 2
                    min_connection_s: 300
                    max_hold_s: 900
                    auto_hold_s: 300
                    accessibility_buffer_s: 300
                    protection: PROTECTED
                    supervision_required: false
                    heartbeat_s: 300
                  respond_by: '2026-11-10T08:16:00-05:00'
      responses:
        '201':
          description: Answered at once; ACCEPTED or REJECTED.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/tender'
              examples:
                feeder_accepted:
                  summary: Feeder leg accepted with terms
                  value:
                    tender_id: tn-tpt-a
                    trip_id: tr-tpt-a
                    interchange_id: ix-tpt
                    respond_by: '2026-11-10T08:10:00-05:00'
                    status: ACCEPTED
                    created_at: '2026-11-10T07:55:00-05:00'
                    accepted_terms:
                      auto_hold_s: 300
                      max_hold_s: 900
                    provider_trip_id: aaata-a-ride-0004
                    responded_at: '2026-11-10T07:55:00-05:00'
                onward_accepted:
                  summary: Onward leg accepted
                  value:
                    tender_id: tn-tpt-b
                    trip_id: tr-tpt-b
                    interchange_id: ix-tpt
                    respond_by: '2026-11-10T08:10:00-05:00'
                    status: ACCEPTED
                    created_at: '2026-11-10T07:55:00-05:00'
                    accepted_terms:
                      auto_hold_s: 300
                      max_hold_s: 900
                    provider_trip_id: oakland-go-0009
                    responded_at: '2026-11-10T07:55:00-05:00'
                retender_accepted:
                  summary: Fallback provider accepts
                  value:
                    tender_id: tn-rt-b2
                    trip_id: tr-rt-b2
                    interchange_id: ix-rt-2
                    respond_by: '2026-11-10T08:16:00-05:00'
                    status: ACCEPTED
                    created_at: '2026-11-10T08:01:00-05:00'
                    accepted_terms:
                      auto_hold_s: 300
                      max_hold_s: 900
                    provider_trip_id: county-connector-0010
                    responded_at: '2026-11-10T08:01:00-05:00'
        '202':
          description: Received; the answer follows as a TENDER_RESPONSE event.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/tender'
              examples:
                offered:
                  summary: Provider will answer later
                  value:
                    tender_id: tn-rt-b
                    trip_id: tr-rt-b
                    interchange_id: ix-rt
                    respond_by: '2026-11-10T08:00:00-05:00'
                    status: OFFERED
                    created_at: '2026-11-10T07:55:00-05:00'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '409':
          description: tender_id or trip_id already in use (code TRIP_ID_IN_USE), or Idempotency-Key reused.
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/error'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
  /tenders/{tender_id}:
    parameters:
    - $ref: '#/components/parameters/TenderId'
    get:
      operationId: getTender
      summary: Get a tender
      description: Current state of a tender, for brokers that poll instead of (or as well as) receiving
        events.
      x-profile: transfer
      tags:
      - Transfer
      security:
      - oauth2:
        - trips:read
      responses:
        '200':
          description: The tender.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/tender'
              examples:
                expired:
                  summary: Unanswered past respond_by
                  value:
                    tender_id: tn-rt-b
                    trip_id: tr-rt-b
                    interchange_id: ix-rt
                    respond_by: '2026-11-10T08:00:00-05:00'
                    status: EXPIRED
                    created_at: '2026-11-10T07:55:00-05:00'
                    responded_at: '2026-11-10T08:01:00-05:00'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
  /subscriptions:
    get:
      operationId: listSubscriptions
      summary: List your subscriptions
      description: 'The authenticated requester''s current subscriptions, oldest first, without signing
        secrets (SPEC.md §9.3). Use it to find subscriptions to change or end, e.g. duplicates made before
        PATCH was available. Optional for providers: those that implement it declare `subscription_management:
        true` on /health.'
      tags:
      - Events
      security:
      - oauth2:
        - events:subscribe
      responses:
        '200':
          description: The requester's subscriptions; empty if it has none.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/subscription-list'
              examples:
                one_subscription:
                  value:
                    subscriptions:
                    - subscription_id: sub-12
                      callback_url: https://broker.example.org/tds/events
                      event_types:
                      - TRIP_STATUS_CHANGED
                      - TRIP_SCHEDULED
                      - TRIP_CANCELLED
                      - TENDER_RESPONSE
                      - HOLD_RESPONSE
                      - MILESTONE
                      - ETA_UPDATE
                      signing_secret_id: sec-1
                      created_at: '2026-10-30T13:00:00-04:00'
                      updated_at: '2026-11-09T09:00:00-05:00'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          description: This endpoint does not list subscriptions over the API (it does not declare subscription_management;
            code NOT_FOUND).
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/error'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
    post:
      operationId: createSubscription
      summary: Register a webhook
      description: 'Register a callback URL for trip events. The response carries the signing secret once;
        events are signed with Standard Webhooks headers (`webhook-signature`) and the deprecated X-TDS-Signature
        (SPEC.md §9, §12). With `cloudevents: true`, deliveries also carry CloudEvents binary-mode `ce-*`
        headers (SPEC.md §9.1). Providers that declare `secret_rotation: true` on /health also return
        `signing_secret_id` and mint later secrets with `POST /subscriptions/{subscription_id}/secrets`
        (SPEC.md §12.1). Providers that declare `subscription_management: true` let the subscriber change
        the event types or callback later with `PATCH /subscriptions/{subscription_id}` instead of subscribing
        again (SPEC.md §9.3).'
      tags:
      - Events
      security:
      - oauth2:
        - events:subscribe
      parameters:
      - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/subscription-request'
            examples:
              all_core_events:
                value:
                  callback_url: https://broker.example.org/tds/events
                  event_types:
                  - TRIP_STATUS_CHANGED
                  - TRIP_SCHEDULED
                  - TRIP_CANCELLED
              with_transfer_events:
                summary: Subscribe to core and transfer events
                value:
                  callback_url: https://broker.example.org/tds/events
                  event_types:
                  - TRIP_STATUS_CHANGED
                  - TRIP_SCHEDULED
                  - TRIP_CANCELLED
                  - TENDER_RESPONSE
                  - HOLD_RESPONSE
                  - MILESTONE
                  - ETA_UPDATE
                  - CUSTODY_HANDOFF
              with_cloudevents_headers:
                summary: Ask for CloudEvents binary-mode headers (SPEC.md §9.1)
                value:
                  callback_url: https://broker.example.org/tds/events
                  event_types:
                  - TRIP_STATUS_CHANGED
                  - TRIP_SCHEDULED
                  - TRIP_CANCELLED
                  cloudevents: true
              before_rotation:
                summary: A subscription whose secret is rotated later (SPEC.md §12.1)
                value:
                  callback_url: https://broker.example.org/tds/events
                  event_types:
                  - TRIP_STATUS_CHANGED
                  - TRIP_SCHEDULED
                  - TRIP_CANCELLED
              before_change:
                summary: A subscription whose event types are changed later (SPEC.md §9.3)
                value:
                  callback_url: https://broker.example.org/tds/events
                  event_types:
                  - TRIP_STATUS_CHANGED
                  - TRIP_SCHEDULED
                  - TRIP_CANCELLED
                  - TENDER_RESPONSE
                  - HOLD_RESPONSE
                  - MILESTONE
      responses:
        '201':
          description: Subscription created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/subscription'
              examples:
                all_core_events:
                  value:
                    subscription_id: sub-01
                    callback_url: https://broker.example.org/tds/events
                    event_types:
                    - TRIP_STATUS_CHANGED
                    - TRIP_SCHEDULED
                    - TRIP_CANCELLED
                    signing_secret: whsec_demo_9f8b2c4e1a7d6f3b5c0e8a2d4f6b1c3e
                    created_at: '2026-10-30T13:00:00-04:00'
                with_transfer_events:
                  value:
                    subscription_id: sub-0001
                    callback_url: https://broker.example.org/tds/events
                    event_types:
                    - TRIP_STATUS_CHANGED
                    - TRIP_SCHEDULED
                    - TRIP_CANCELLED
                    - TENDER_RESPONSE
                    - HOLD_RESPONSE
                    - MILESTONE
                    - ETA_UPDATE
                    - CUSTODY_HANDOFF
                    signing_secret: whsec_example_not_a_real_secret_0000
                    created_at: '2026-11-10T07:55:00-05:00'
                    signing_secret_id: sec-1
                with_cloudevents_headers:
                  value:
                    subscription_id: sub-02
                    callback_url: https://broker.example.org/tds/events
                    event_types:
                    - TRIP_STATUS_CHANGED
                    - TRIP_SCHEDULED
                    - TRIP_CANCELLED
                    cloudevents: true
                    signing_secret: whsec_gaDNkYMCuFPZn6nIBDzOAbSUk4511sYvoUancitvONQ=
                    created_at: '2026-10-30T13:00:00-04:00'
                before_rotation:
                  value:
                    subscription_id: sub-07
                    callback_url: https://broker.example.org/tds/events
                    event_types:
                    - TRIP_STATUS_CHANGED
                    - TRIP_SCHEDULED
                    - TRIP_CANCELLED
                    signing_secret: whsec_iXef3xFah5RUykuw+gB/LTK+UBI26zKYoHI9LsRl/Eo=
                    signing_secret_id: sec-01
                    created_at: '2026-10-30T13:00:00-04:00'
                before_change:
                  value:
                    subscription_id: sub-12
                    callback_url: https://broker.example.org/tds/events
                    event_types:
                    - TRIP_STATUS_CHANGED
                    - TRIP_SCHEDULED
                    - TRIP_CANCELLED
                    - TENDER_RESPONSE
                    - HOLD_RESPONSE
                    - MILESTONE
                    signing_secret: whsec_ouR5t41ZVTDLNklo0wnYBitpyVqM+IOzIKj5LgCcZx4=
                    signing_secret_id: sec-1
                    created_at: '2026-10-30T13:00:00-04:00'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
  /subscriptions/{subscription_id}:
    parameters:
    - $ref: '#/components/parameters/SubscriptionId'
    get:
      operationId: getSubscription
      summary: Read a subscription
      description: 'The subscription''s callback, event types and settings (SPEC.md §9.3), without its
        signing secret. Optional for providers: those that implement it declare `subscription_management:
        true` on /health.'
      tags:
      - Events
      security:
      - oauth2:
        - events:subscribe
      responses:
        '200':
          description: The subscription, without signing_secret.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/subscription'
              examples:
                core_and_transfer_events:
                  value:
                    subscription_id: sub-12
                    callback_url: https://broker.example.org/tds/events
                    event_types:
                    - TRIP_STATUS_CHANGED
                    - TRIP_SCHEDULED
                    - TRIP_CANCELLED
                    - TENDER_RESPONSE
                    - HOLD_RESPONSE
                    - MILESTONE
                    signing_secret_id: sec-1
                    created_at: '2026-10-30T13:00:00-04:00'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          description: No such subscription for the authenticated requester, including one it has ended
            (code NOT_FOUND).
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/error'
              examples:
                ended:
                  value:
                    type: about:blank
                    title: Not Found
                    status: 404
                    detail: No subscription 'sub-12' for this requester.
                    code: NOT_FOUND
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
    patch:
      operationId: updateSubscription
      summary: Change a subscription
      description: 'Replace the subscription''s event types, callback URL or CloudEvents setting (SPEC.md
        §9.3); fields left out keep their values, and `event_types` is replaced as a whole. The subscription_id,
        signing secrets and secret ids do not change, so deliveries keep verifying with the secret the
        subscriber already holds. Events raised after the response follow the new settings. A retry with
        the same Idempotency-Key returns the same answer. Optional for providers: those that implement
        it declare `subscription_management: true` on /health.'
      tags:
      - Events
      security:
      - oauth2:
        - events:subscribe
      parameters:
      - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/subscription-update'
            examples:
              add_eta_update:
                summary: Add ETA_UPDATE to the event types
                value:
                  event_types:
                  - TRIP_STATUS_CHANGED
                  - TRIP_SCHEDULED
                  - TRIP_CANCELLED
                  - TENDER_RESPONSE
                  - HOLD_RESPONSE
                  - MILESTONE
                  - ETA_UPDATE
      responses:
        '200':
          description: The changed subscription, with updated_at and without signing_secret.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/subscription'
              examples:
                add_eta_update:
                  value:
                    subscription_id: sub-12
                    callback_url: https://broker.example.org/tds/events
                    event_types:
                    - TRIP_STATUS_CHANGED
                    - TRIP_SCHEDULED
                    - TRIP_CANCELLED
                    - TENDER_RESPONSE
                    - HOLD_RESPONSE
                    - MILESTONE
                    - ETA_UPDATE
                    signing_secret_id: sec-1
                    created_at: '2026-10-30T13:00:00-04:00'
                    updated_at: '2026-11-09T09:00:00-05:00'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/SubscriptionNotFound'
        '409':
          description: Idempotency-Key reused with a different body (code IDEMPOTENCY_KEY_REUSED).
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/error'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
    delete:
      operationId: deleteSubscription
      summary: End a subscription
      description: 'End the subscription (SPEC.md §9.3): no delivery attempt to it starts after the response,
        retries of earlier events included, and its signing secrets stop being used. Reading, changing
        or ending it again is NOT_FOUND; a retry with the same Idempotency-Key returns the same answer.
        Optional for providers: those that implement it declare `subscription_management: true` on /health.'
      tags:
      - Events
      security:
      - oauth2:
        - events:subscribe
      parameters:
      - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '200':
          description: The subscription as it was when it ended, without signing_secret.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/subscription'
              examples:
                ended:
                  value:
                    subscription_id: sub-12
                    callback_url: https://broker.example.org/tds/events
                    event_types:
                    - TRIP_STATUS_CHANGED
                    - TRIP_SCHEDULED
                    - TRIP_CANCELLED
                    - TENDER_RESPONSE
                    - HOLD_RESPONSE
                    - MILESTONE
                    - ETA_UPDATE
                    signing_secret_id: sec-1
                    created_at: '2026-10-30T13:00:00-04:00'
                    updated_at: '2026-11-09T09:00:00-05:00'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/SubscriptionNotFound'
        '409':
          description: Idempotency-Key reused with a different request (code IDEMPOTENCY_KEY_REUSED).
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/error'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
  /subscriptions/{subscription_id}/secrets:
    parameters:
    - $ref: '#/components/parameters/SubscriptionId'
    post:
      operationId: rotateSubscriptionSecret
      summary: Rotate a subscription's signing secret
      description: 'Mint a new signing secret for the subscription (SPEC.md §12.1). The response carries
        the new value once, as the first entry; older secrets stay valid until their expires_at (now +
        overlap_s, or the provider''s default overlap), and until then every delivery carries a `webhook-signature`
        entry for each active secret, newest first, while X-TDS-Signature uses the newest. A retry with
        the same Idempotency-Key returns the same new secret. Optional for providers: those that implement
        it declare `secret_rotation: true` on /health.'
      tags:
      - Events
      security:
      - oauth2:
        - events:subscribe
      parameters:
      - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/secret-rotation-request'
            examples:
              one_day_overlap:
                value:
                  overlap_s: 86400
      responses:
        '201':
          description: New secret created. The first entry is the new secret, with its value; the others
            are the older secrets still active, with expires_at.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/subscription-secret-list'
              examples:
                one_day_overlap:
                  value:
                    subscription_id: sub-07
                    secrets:
                    - secret_id: sec-02
                      signing_secret: whsec_1WNy36llSMlCdz1DmnU4LvvPLdPydBGDi759pN8lE9I=
                      created_at: '2026-11-02T09:00:00-05:00'
                    - secret_id: sec-01
                      created_at: '2026-10-30T13:00:00-04:00'
                      expires_at: '2026-11-03T09:00:00-05:00'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/SubscriptionNotFound'
        '409':
          description: The provider already holds as many active secrets as it allows (code INVALID_STATE);
            retire one or wait for one to expire.
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/error'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
    get:
      operationId: listSubscriptionSecrets
      summary: List a subscription's active signing secrets
      description: The subscription's active secrets, newest first, with ids and expiry times but never
        their values (SPEC.md §12.1).
      tags:
      - Events
      security:
      - oauth2:
        - events:subscribe
      responses:
        '200':
          description: Active secrets, newest first.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/subscription-secret-list'
              examples:
                during_overlap:
                  value:
                    subscription_id: sub-07
                    secrets:
                    - secret_id: sec-02
                      created_at: '2026-11-02T09:00:00-05:00'
                    - secret_id: sec-01
                      created_at: '2026-10-30T13:00:00-04:00'
                      expires_at: '2026-11-03T09:00:00-05:00'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/SubscriptionNotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
  /subscriptions/{subscription_id}/secrets/{secret_id}:
    parameters:
    - $ref: '#/components/parameters/SubscriptionId'
    - $ref: '#/components/parameters/SecretId'
    delete:
      operationId: retireSubscriptionSecret
      summary: Retire a signing secret early
      description: Stop signing with an older secret now, before its expires_at, once the receiver verifies
        with the newer one (SPEC.md §12.1). The newest secret cannot be retired; rotate first. Retiring
        a secret that has already expired changes nothing and returns it.
      tags:
      - Events
      security:
      - oauth2:
        - events:subscribe
      parameters:
      - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '200':
          description: The secret, with expires_at no later than now. Its value is not returned.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/subscription-secret'
              examples:
                retired:
                  value:
                    secret_id: sec-01
                    created_at: '2026-10-30T13:00:00-04:00'
                    expires_at: '2026-11-02T12:00:00-05:00'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          description: No such subscription for the authenticated requester, or no such secret in it (code
            NOT_FOUND).
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/error'
        '409':
          description: The secret is the subscription's newest one (code INVALID_STATE); rotate first.
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/error'
              examples:
                newest:
                  value:
                    type: about:blank
                    title: Invalid state
                    status: 409
                    detail: sec-02 is the newest secret of sub-07; rotate before retiring it.
                    code: INVALID_STATE
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
  /health:
    get:
      operationId: getHealth
      summary: Health and capabilities
      description: Liveness, supported spec versions and profiles, how the endpoint is operated, and the
        booking windows within which it accepts requests (SPEC.md §7.1). No authentication required.
      tags:
      - Meta
      security: []
      responses:
        '200':
          description: Endpoint is up.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/health'
              examples:
                direct:
                  summary: Provider runs its own endpoint
                  value:
                    status: OK
                    spec_versions:
                    - 0.4.0
                    profiles:
                    - CORE
                    operated_via: DIRECT
                broker_proxy:
                  summary: Broker fronts a provider without software
                  value:
                    status: OK
                    spec_versions:
                    - 0.4.0
                    profiles:
                    - CORE
                    operated_via: BROKER_PROXY
                    typical_response_s: 900
                booking_window:
                  summary: Books 14 days ahead with 24 hours' notice; paratransit next day only
                  value:
                    status: OK
                    spec_versions:
                    - 0.5.3
                    profiles:
                    - CORE
                    - PARATRANSIT
                    operated_via: DIRECT
                    booking_window:
                      max_advance_s: 1209600
                      min_notice_s: 86400
                      time_zone: America/Detroit
                    booking_window_by_profile:
                      paratransit:
                        max_advance_s: 172800
                        same_day: false
                        time_zone: America/Detroit
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
components:
  securitySchemes:
    oauth2:
      type: oauth2
      description: OAuth 2.0 client credentials (SPEC.md §12). Each deployment publishes its own token
        endpoint; the URL here is relative to the server. mTLS may be layered on top by agreement.
      flows:
        clientCredentials:
          tokenUrl: /oauth/token
          scopes:
            trips:read: Read trips this client requested.
            trips:write: Request, confirm, modify and cancel trips.
            events:subscribe: Register, read, change and end webhook subscriptions and rotate their secrets.
            tenders:write: Send tenders (transfer profile).
  parameters:
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: true
      description: Client-generated key (e.g. a UUID) making the request safe to retry. A repeat with
        the same key and body returns the original response; the same key with a different body is rejected
        with IDEMPOTENCY_KEY_REUSED. Keys are scoped to the authenticated requester and kept for at least
        24 hours.
      schema:
        type: string
        minLength: 8
        maxLength: 128
    TripId:
      name: trip_id
      in: path
      required: true
      description: The requester-assigned trip_id. Scoped to the authenticated requester.
      schema:
        type: string
    HoldId:
      name: hold_id
      in: path
      required: true
      description: The broker-assigned hold_id. Scoped to the authenticated requester.
      schema:
        type: string
    TenderId:
      name: tender_id
      in: path
      required: true
      description: The broker-assigned tender_id. Scoped to the authenticated requester.
      schema:
        type: string
    SubscriptionId:
      name: subscription_id
      in: path
      required: true
      description: 'The provider-assigned subscription_id. Scoped to the authenticated requester: another
        client''s subscription is NOT_FOUND.'
      schema:
        type: string
    SecretId:
      name: secret_id
      in: path
      required: true
      description: The provider-assigned secret_id of one of the subscription's signing secrets.
      schema:
        type: string
  schemas:
    contact:
      title: Contact
      description: How a provider may reach the rider about a trip, e.g. for arrival notices.
      type: object
      required:
      - phone
      properties:
        phone:
          description: Phone number able to receive SMS, in E.164 format.
          type: string
          pattern: ^\+[1-9][0-9]{1,14}$
    eligibility:
      title: Eligibility
      description: 'An attestation that the rider is eligible for a program, or meets a fact a program
        depends on (an age threshold, disability status, low income or county of residence). Carries the
        program or fact, who vouches for it and how it was verified, never the evidence behind it: no
        date of birth, document or card numbers, document images, credentials or diagnoses. A provider
        decides which combinations of `attested_by` and `verification_method` it accepts (SPEC.md §6.5.1)
        and refuses others with ELIGIBILITY_NOT_ACCEPTED. Rule JSON Schema can''t express (checked by
        the conformance kit): `verified_at` is not after `valid_until`.'
      type: object
      required:
      - program
      - attested_by
      properties:
        program:
          description: 'Program code (e.g. ADA_PARATRANSIT, SENIOR_5310) or fact code: what is true of
            the rider for program purposes, never why (SPEC.md §6.5.2). Fact codes SHOULD follow AGE_<N>_PLUS
            / AGE_UNDER_<N> (an age threshold, never the birth date), DISABILITY (yes/no only, with no
            qualifier: never a condition, diagnosis or type), LOW_INCOME (meets the named program''s income
            test, never the income) or RESIDENT_<country>_<area> (RESIDENT_US_<5-digit county FIPS>, e.g.
            RESIDENT_US_26161; never the address). Send only the facts the receiving provider''s agreement
            asks for. Open vocabulary until the crosswalk settles a list.'
          type: string
          pattern: ^[A-Z][A-Z0-9_]*$
        attested_by:
          description: Identifier of the party (agency, provider or broker) that attests eligibility.
          type: string
        valid_until:
          description: Last date the attestation is valid, if it expires.
          type: string
          format: date
        verification_method:
          description: 'How the attesting party established eligibility. STAFF_VERIFIED: a person checked
            documents. DIGITAL_ID: an identity platform, mobile ID or verifiable credential disclosed
            the fact (e.g. an age-over claim) without the underlying data. AGENCY_DETERMINATION: a formal
            eligibility determination, such as ADA paratransit certification. SELF_DECLARED: the rider''s
            own statement, unchecked. OTHER: none of these. Absent means not stated; a provider may treat
            that like SELF_DECLARED.'
          type: string
          enum:
          - STAFF_VERIFIED
          - DIGITAL_ID
          - AGENCY_DETERMINATION
          - SELF_DECLARED
          - OTHER
        verified_at:
          description: Date the attesting party last verified eligibility.
          type: string
          format: date
        verifier_ref:
          description: Opaque identifier of the identity platform or credential issuer that verified the
            fact, when it is not `attested_by` (e.g. a state mobile ID issuer or an OpenID provider).
            Names the verifier only; never a credential, token, document number or anything that identifies
            the rider.
          type: string
          maxLength: 200
    rider:
      title: Rider
      description: A reference to the rider, carrying the minimum a provider needs to serve one leg. The
        requester owns rider identity; no date of birth, diagnosis or other health data belongs here.
        Contact details may only be sent with consent.
      type: object
      required:
      - rider_ref_id
      properties:
        rider_ref_id:
          description: Opaque rider identifier, scoped to the requester. The requester SHOULD issue a
            different value for each provider (a pairwise pseudonym) and SHOULD keep it stable for a given
            rider and provider, so providers cannot link a rider across services.
          type: string
        display_name:
          description: How the driver should address the rider, e.g. first name or initial.
          type: string
          maxLength: 60
        contact:
          description: Rider contact details. Only present when contact_consent is true.
          $ref: '#/components/schemas/contact'
        contact_consent:
          description: True if the rider has agreed to be contacted by the provider. Managed by the requester.
          type: boolean
          default: false
        language:
          description: Preferred language as a BCP 47 tag, e.g. "en" or "es-MX".
          type: string
          pattern: ^[A-Za-z]{2,3}(-[A-Za-z0-9]{2,8})*$
        eligibility:
          description: Program eligibility and fact attestations (e.g. AGE_60_PLUS, DISABILITY, RESIDENT_US_26161)
            relevant to this trip, each saying who attests it and how it was verified. Attest the fact
            a program needs instead of sending the data behind it, and only the facts the receiving provider
            asks for (SPEC.md §6.5.2). Providers derive reporting categories (senior, disabled) from these
            facts; a missing fact means general or unknown.
          type: array
          items:
            $ref: '#/components/schemas/eligibility'
      dependentSchemas:
        contact:
          required:
          - contact_consent
          properties:
            contact_consent:
              const: true
    party:
      title: Party
      description: People and animals travelling with the rider, so the provider can reserve capacity.
        Counts only; no identities.
      type: object
      properties:
        companions:
          description: Number of companions travelling with the rider.
          type: integer
          minimum: 0
          default: 0
        attendants:
          description: Number of personal care attendants travelling with the rider.
          type: integer
          minimum: 0
          default: 0
        service_animals:
          description: Number of service animals travelling with the rider.
          type: integer
          minimum: 0
          default: 0
    address:
      title: Address
      description: Structured postal address. Supplements, never replaces, coordinates on a Location.
      type: object
      properties:
        street:
          description: Street number and name, including unit if relevant.
          type: string
          maxLength: 200
        city:
          description: City, town or municipality.
          type: string
          maxLength: 100
        region:
          description: State, province or region; US states use the two-letter USPS code.
          type: string
          maxLength: 100
        postal_code:
          description: Postal or ZIP code.
          type: string
          maxLength: 20
        country:
          description: ISO 3166-1 alpha-2 country code.
          type: string
          pattern: ^[A-Z]{2}$
    location:
      title: Location
      description: A pickup, dropoff or transfer point. Coordinates are WGS84 and always required; GTFS
        / GTFS-Flex identifiers are given when the point is a published stop or zone.
      type: object
      required:
      - lat
      - lon
      properties:
        lat:
          description: Latitude in decimal degrees (WGS84).
          type: number
          minimum: -90
          maximum: 90
        lon:
          description: Longitude in decimal degrees (WGS84).
          type: number
          minimum: -180
          maximum: 180
        name:
          description: Human-readable place name, e.g. a facility or landmark.
          type: string
          maxLength: 200
        address:
          description: Structured postal address of the point.
          $ref: '#/components/schemas/address'
        gtfs_stop_id:
          description: GTFS `stop_id` in the provider's published feed, if the point is a stop.
          type: string
        flex_location_id:
          description: GTFS-Flex location or location-group id, if the point is a Flex zone.
          type: string
        access_notes:
          description: Wayfinding for the driver, e.g. "east entrance". Not for rider health or condition
            information.
          type: string
          maxLength: 300
    time-window:
      title: TimeWindow
      description: A span of time within which an event (pickup or dropoff) should happen. Producers MUST
        ensure earliest <= preferred <= latest; this ordering cannot be expressed in JSON Schema and is
        checked by the conformance kit.
      type: object
      required:
      - earliest
      - latest
      properties:
        earliest:
          description: Start of the window, RFC 3339 with offset.
          type: string
          format: date-time
        latest:
          description: End of the window, RFC 3339 with offset.
          type: string
          format: date-time
        preferred:
          description: Time the rider would like, if any. Must fall inside the window.
          type: string
          format: date-time
    vehicle-ref:
      title: VehicleRef
      description: Identifies the vehicle assigned to a leg so the rider or an attendant can recognise
        it. Typically set by the provider shortly before pickup.
      type: object
      required:
      - vehicle_id
      properties:
        vehicle_id:
          description: Provider's opaque vehicle identifier.
          type: string
        description:
          description: Rider-facing description, e.g. "White Ford Transit,
          type: string
          maxLength: 200
        plate:
          description: Licence plate as displayed on the vehicle.
          type: string
          maxLength: 20
    leg:
      title: Leg
      description: 'One pickup-to-dropoff movement performed by a single provider. Exactly one of the
        requested windows anchors the leg: pickup_window for "leave at" trips, dropoff_window for appointment
        ("arrive by") trips. Both may be given.'
      type: object
      required:
      - leg_id
      - pickup
      - dropoff
      anyOf:
      - required:
        - pickup_window
      - required:
        - dropoff_window
      properties:
        leg_id:
          description: Identifier of the leg, unique within its trip.
          type: string
        pickup:
          description: Where the rider is picked up.
          $ref: '#/components/schemas/location'
        dropoff:
          description: Where the rider is dropped off.
          $ref: '#/components/schemas/location'
        pickup_window:
          description: Requested pickup window.
          $ref: '#/components/schemas/time-window'
        dropoff_window:
          description: Requested dropoff window; used for appointment-anchored trips.
          $ref: '#/components/schemas/time-window'
        scheduled_pickup:
          description: Pickup time committed by the provider. Set on confirmation or scheduling.
          type: string
          format: date-time
        scheduled_dropoff:
          description: Dropoff time committed by the provider, if it commits to one.
          type: string
          format: date-time
        service_requirements:
          description: 'Services the vehicle and driver must provide. Expresses what is needed, not why:
            no diagnoses or conditions. HAND_TO_HAND means the driver may release the rider only to a
            responsible person at the destination. CHILD_SEAT means the vehicle must carry an appropriate
            child restraint. STRETCHER means the vehicle carries the rider lying down, with staff to load
            them.'
          type: array
          uniqueItems: true
          items:
            type: string
            enum:
            - WHEELCHAIR_LIFT
            - RAMP
            - DOOR_TO_DOOR
            - HAND_TO_HAND
            - ATTENDANT_REQUIRED
            - NO_STEPS
            - CHILD_SEAT
            - STRETCHER
        vehicle_ref:
          description: Assigned vehicle, once known.
          $ref: '#/components/schemas/vehicle-ref'
    money:
      title: Money
      description: An amount of money. The amount is a decimal string so it survives JSON round-trips
        without floating-point error.
      type: object
      required:
      - amount
      - currency
      properties:
        amount:
          description: Non-negative decimal amount in the major unit, e.g. "2.50".
          type: string
          pattern: ^[0-9]+(\.[0-9]{1,4})?$
        currency:
          description: ISO 4217 currency code.
          type: string
          pattern: ^[A-Z]{3}$
    funding-source:
      title: FundingSource
      description: Who pays for a trip or leg. SECTION_5310 and SECTION_5311 are the US federal transit
        programs (49 U.S.C. 5310 and 5311); MEDICAID_NEMT is Medicaid non-emergency medical transportation;
        AGENCY is the provider's or requester's own budget; SPONSOR is a third party named by sponsor_ref.
        Settlement data only; no payment is processed through this API.
      type: string
      enum:
      - RIDER
      - SECTION_5310
      - SECTION_5311
      - MEDICAID_NEMT
      - AGENCY
      - SPONSOR
      - OTHER
    fare-funding:
      title: FareFunding
      description: Who pays for the trip and under what authorization. Data for settlement only; no payment
        is processed through this API.
      type: object
      properties:
        fare:
          description: Fare charged for the trip, if known at booking.
          $ref: '#/components/schemas/money'
        funding_source:
          description: Primary funding source for the trip.
          $ref: '#/components/schemas/funding-source'
        sponsor_ref:
          description: Identifier of the sponsoring organization, when funding_source is SPONSOR.
          type: string
        authorization_ref:
          description: Reference to a funding authorization held by the funder (e.g. a Medicaid prior-authorization
            number). A reference only; no clinical content.
          type: string
    trip-request:
      title: TripRequest
      description: 'Body of `POST /trips`. The requester''s view of a trip before the provider has decided:
        fields the provider sets (status, provider_trip_id, timestamps, scheduled times, vehicle) are
        absent or ignored.'
      type: object
      required:
      - trip_id
      - requester_id
      - provider_id
      - rider_ref
      - legs
      properties:
        trip_id:
          description: Identifier assigned by the requester; unique per requester_id. Same as trip.trip_id.
          type: string
        requester_id:
          description: Identifier of the party requesting the trip. Must match the authenticated client.
          type: string
        provider_id:
          description: Identifier of the provider asked to serve the trip.
          type: string
        rider_ref:
          description: The rider, by reference.
          $ref: '#/components/schemas/rider'
        party:
          description: Others travelling with the rider.
          $ref: '#/components/schemas/party'
        legs:
          description: The requested leg. Core requires exactly one.
          type: array
          minItems: 1
          maxItems: 1
          items:
            $ref: '#/components/schemas/leg'
        fare_funding:
          description: Fare and funding details for settlement.
          $ref: '#/components/schemas/fare-funding'
        notes_to_driver:
          description: Free-text operational notes. Must not contain health or diagnosis information.
          type: string
          maxLength: 500
    trip-status:
      title: TripStatus
      description: 'Lifecycle state of a trip (SPEC.md §8.1). Terminal states: COMPLETED, FAILED, REJECTED,
        EXPIRED, CANCELLED, NO_SHOW. Allowed transitions are enforced by the conformance kit, not by this
        schema.'
      type: string
      enum:
      - REQUESTED
      - OFFERED
      - CONFIRMED
      - SCHEDULED
      - IN_PROGRESS
      - COMPLETED
      - FAILED
      - REJECTED
      - EXPIRED
      - CANCELLED
      - NO_SHOW
    cancel-reason:
      title: CancelReason
      description: Reason code for a cancellation. Codes, not free text, so events carry no personal information.
        MISSED_CONNECTION is used when an upstream leg in a transfer failed.
      type: string
      enum:
      - RIDER_REQUEST
      - PROVIDER_UNABLE
      - DUPLICATE
      - MISSED_CONNECTION
      - OTHER
    cancellation:
      title: Cancellation
      description: Record of a trip's cancellation, set by the provider when the trip becomes CANCELLED.
      type: object
      required:
      - reason
      - cancelled_by
      - cancelled_at
      properties:
        reason:
          description: Why the trip was cancelled.
          $ref: '#/components/schemas/cancel-reason'
        cancelled_by:
          description: Which side cancelled.
          type: string
          enum:
          - REQUESTER
          - PROVIDER
        detail:
          description: Optional free-text explanation. Must not contain health or diagnosis information.
          type: string
          maxLength: 500
        cancelled_at:
          description: When the cancellation took effect, RFC 3339 with offset.
          type: string
          format: date-time
    trip:
      title: Trip
      description: A trip requested by a requester and served by one provider. In core a trip has exactly
        one leg; composing several providers' trips into one journey is a broker concern handled by the
        transfer profile.
      type: object
      required:
      - trip_id
      - requester_id
      - provider_id
      - status
      - rider_ref
      - legs
      - created_at
      - updated_at
      properties:
        trip_id:
          description: Identifier assigned by the requester; unique per requester_id.
          type: string
        provider_trip_id:
          description: Identifier assigned by the provider once it accepts the trip.
          type: string
        requester_id:
          description: Identifier of the party requesting the trip.
          type: string
        provider_id:
          description: Identifier of the party asked to serve the trip.
          type: string
        status:
          description: Current lifecycle state.
          $ref: '#/components/schemas/trip-status'
        rider_ref:
          description: The rider, by reference.
          $ref: '#/components/schemas/rider'
        party:
          description: Others travelling with the rider.
          $ref: '#/components/schemas/party'
        legs:
          description: The trip's legs. Core requires exactly one.
          type: array
          minItems: 1
          maxItems: 1
          items:
            $ref: '#/components/schemas/leg'
        fare_funding:
          description: Fare and funding details for settlement.
          $ref: '#/components/schemas/fare-funding'
        notes_to_driver:
          description: Free-text operational notes, e.g. "call on arrival". Must not contain health or
            diagnosis information.
          type: string
          maxLength: 500
        offer_expires_at:
          description: 'While the trip is OFFERED: the offer lapses (status EXPIRED) unless the requester
            confirms by then. Absent when offers don''t expire.'
          type: string
          format: date-time
        cancellation:
          description: Present when status is CANCELLED.
          $ref: '#/components/schemas/cancellation'
        created_at:
          description: When the trip was created, RFC 3339 with offset.
          type: string
          format: date-time
        updated_at:
          description: When the trip last changed, RFC 3339 with offset.
          type: string
          format: date-time
    refusal-reason:
      title: RefusalReason
      description: 'Why a provider refuses to serve a trip, tender or change. Used as the `code` of a
        422 Problem Details response and as the `reason` on a rejected tender. ELIGIBILITY_REQUIRED: the
        trip needs an eligibility attestation the request does not carry. ELIGIBILITY_NOT_ACCEPTED: the
        request carries one, but the provider does not accept its attesting party or verification method
        (SPEC.md §6.5.1); the detail may say which, never anything about the rider. OUTSIDE_BOOKING_WINDOW:
        the request came too early or too late for the provider''s booking window (SPEC.md §7.1, `/health`
        booking_window); when too early, the Problem Details carry `bookable_from`. A later request is
        a new trip (new trip_id).'
      type: string
      enum:
      - NO_CAPACITY
      - OUTSIDE_SERVICE_AREA
      - OUTSIDE_SERVICE_HOURS
      - ELIGIBILITY_REQUIRED
      - ELIGIBILITY_NOT_ACCEPTED
      - OUTSIDE_BOOKING_WINDOW
      - INVALID_WINDOW
      - UNSUPPORTED_REQUIREMENT
    error:
      title: Error
      description: RFC 9457 Problem Details (media type application/problem+json) with a machine-readable
        `code` so requesters can react without parsing text.
      type: object
      required:
      - title
      - status
      - code
      properties:
        type:
          description: URI identifying the problem type. Defaults to "about:blank".
          type: string
          format: uri-reference
          default: about:blank
        title:
          description: Short human-readable summary of the problem type.
          type: string
        status:
          description: HTTP status code generated by the origin server.
          type: integer
          minimum: 100
          maximum: 599
        detail:
          description: Human-readable explanation specific to this occurrence.
          type: string
        instance:
          description: URI reference identifying this occurrence.
          type: string
          format: uri-reference
        code:
          description: 'Machine-readable reason: a protocol error, or a RefusalReason (a 422 with one
            of these leaves the trip REJECTED). TRIP_ID_IN_USE also covers a reused tender_id or hold_id.
            RATE_LIMITED (429) and TEMPORARILY_UNAVAILABLE (503) are back-pressure (SPEC.md §7.2): the
            request was not processed and may be retried with the same Idempotency-Key after retry_after_s.
            INVALID_FILE_NAME, EMPTY_FILE, FILE_TOO_LARGE and FILE_NAME_TAKEN are a relay mailbox''s refusals
            of an upload (SPEC.md §9.4); NOT_FOUND there means no such file in this mailbox. Unknown values
            must be treated as a generic failure.'
          anyOf:
          - type: string
            enum:
            - INVALID_REQUEST
            - UNAUTHORIZED
            - NOT_FOUND
            - INVALID_STATE
            - IDEMPOTENCY_KEY_REUSED
            - TRIP_ID_IN_USE
            - RATE_LIMITED
            - TEMPORARILY_UNAVAILABLE
            - INVALID_FILE_NAME
            - EMPTY_FILE
            - FILE_TOO_LARGE
            - FILE_NAME_TAKEN
          - $ref: '#/components/schemas/refusal-reason'
        bookable_from:
          description: 'With code OUTSIDE_BOOKING_WINDOW, when the request came too early: the first moment
            a request for this trip will be inside the provider''s booking window (trip time minus max_advance_s).
            Absent when waiting will not help (too little notice).'
          type: string
          format: date-time
        retry_after_s:
          description: 'Back-pressure hint (SPEC.md §7.2): the least number of seconds the client should
            wait before retrying this request. Same value as the Retry-After response header (RFC 9110
            §10.2.3), for clients that cannot read headers; when both are sent they SHOULD agree, and
            a client that sees both uses the larger. A provider SHOULD send it with 429 and 503, and MAY
            send it with any other 5xx. The retry repeats the same request with the same Idempotency-Key.'
          type: integer
          minimum: 0
    trip-modification:
      title: TripModification
      description: 'Body of `PATCH /trips/{trip_id}`: the fields a requester may change after booking.
        Windows apply to the trip''s single leg. Omitted fields are unchanged. The provider may accept
        the change, re-offer (status OFFERED) or reject it.'
      type: object
      minProperties: 1
      properties:
        pickup_window:
          description: New requested pickup window.
          $ref: '#/components/schemas/time-window'
        dropoff_window:
          description: New requested dropoff window.
          $ref: '#/components/schemas/time-window'
        party:
          description: New party counts; replaces the previous party object.
          $ref: '#/components/schemas/party'
        notes_to_driver:
          description: Replacement driver notes. Must not contain health or diagnosis information.
          type: string
          maxLength: 500
    cancel-request:
      title: CancelRequest
      description: Body of `POST /trips/{trip_id}/cancel`, sent by the requester.
      type: object
      required:
      - reason
      properties:
        reason:
          description: Why the trip is cancelled.
          $ref: '#/components/schemas/cancel-reason'
        detail:
          description: Optional free-text explanation. Must not contain health or diagnosis information.
          type: string
          maxLength: 500
    delay-cause:
      title: DelayCause
      x-profile: transfer
      description: Why a leg is late or failed, modeled on rail delay attribution. Codes only, no free
        text, and no rider-attributed cause. Whether a cause shifts cost is for inter-provider agreements,
        not this spec.
      type: string
      enum:
      - TRAFFIC
      - BOARDING
      - VEHICLE
      - WEATHER
      - PRIOR_TRIP
      - UNKNOWN
    eta-band:
      title: EtaBand
      x-profile: transfer
      description: Estimated arrival at one end of a leg as a band rather than a point (SPEC.md §10.4).
        Requires earliest <= likely <= latest.
      type: object
      required:
      - target
      - earliest
      - likely
      - latest
      - confidence
      - computed_at
      properties:
        target:
          description: Which end of the leg the estimate is for.
          type: string
          enum:
          - PICKUP
          - DROPOFF
        earliest:
          description: Earliest plausible arrival.
          type: string
          format: date-time
        likely:
          description: Most likely arrival.
          type: string
          format: date-time
        latest:
          description: Latest plausible arrival.
          type: string
          format: date-time
        confidence:
          description: Probability that arrival is no later than `latest`.
          type: number
          minimum: 0
          maximum: 1
        computed_at:
          description: When the estimate was made.
          type: string
          format: date-time
        source:
          description: Where the estimate comes from. SCHEDULE means only the promised time is known;
            brokers may ask for fresher status sooner when the source is weak.
          type: string
          enum:
          - VEHICLE_POSITION
          - PROVIDER_FORECAST
          - DISPATCHER
          - DRIVER
          - SCHEDULE
        delay_cause:
          description: Why the leg is running late, if it is and the cause is known.
          $ref: '#/components/schemas/delay-cause'
    hold-request:
      title: HoldRequest
      x-profile: transfer
      description: Body of `POST /trips/{trip_id}/hold`, sent by the broker to the outbound provider when
        the inbound leg's ETA threatens the connection (SPEC.md §10.5). HOLD asks the vehicle to wait
        until requested_until; RETIME proposes requested_until as the new pickup time. Re-protecting with
        another provider is broker-side (cancel with MISSED_CONNECTION, then a new tender), not a hold.
      type: object
      required:
      - hold_id
      - interchange_id
      - action
      - requested_until
      - respond_by
      properties:
        hold_id:
          description: Broker-assigned identifier, unique per requester.
          type: string
        interchange_id:
          description: The interchange whose outbound leg is this trip.
          type: string
        action:
          description: HOLD (wait) or RETIME (move the pickup).
          type: string
          enum:
          - HOLD
          - RETIME
        requested_until:
          description: 'HOLD: wait until this time. RETIME: the proposed new pickup time.'
          type: string
          format: date-time
        respond_by:
          description: Deadline for an answer. Unanswered by then, the hold is EXPIRED.
          type: string
          format: date-time
        inbound_eta:
          description: The inbound leg's latest ETA band, so the provider can judge the request.
          $ref: '#/components/schemas/eta-band'
    hold:
      title: Hold
      x-profile: transfer
      description: State of a hold request at the outbound provider. PENDING until answered (synchronously,
        or later via a HOLD_RESPONSE event). GRANTED and PARTIAL carry the resulting scheduled_pickup;
        a provider counter-offer ("can't wait that long, can pick up at 10:15") is PARTIAL with that time.
      type: object
      required:
      - hold_id
      - trip_id
      - interchange_id
      - action
      - requested_until
      - respond_by
      - status
      - created_at
      properties:
        hold_id:
          description: Broker-assigned hold identifier.
          type: string
        trip_id:
          description: The outbound trip.
          type: string
        interchange_id:
          description: The interchange being protected.
          type: string
        action:
          description: What was asked.
          type: string
          enum:
          - HOLD
          - RETIME
        requested_until:
          description: As in the request.
          type: string
          format: date-time
        respond_by:
          description: As in the request.
          type: string
          format: date-time
        status:
          description: Hold lifecycle state.
          type: string
          enum:
          - PENDING
          - GRANTED
          - PARTIAL
          - DENIED
          - EXPIRED
        scheduled_pickup:
          description: Outbound pickup time after the decision (GRANTED or PARTIAL).
          type: string
          format: date-time
        held_until:
          description: Latest time the vehicle will wait at the transfer point, if different from scheduled_pickup.
          type: string
          format: date-time
        reason:
          description: Why the hold was denied or only partly granted.
          type: string
          enum:
          - EXCEEDS_MAX_HOLD
          - DOWNSTREAM_COMMITMENT
          - DRIVER_HOURS
          - OTHER
        created_at:
          description: When the provider received the request.
          type: string
          format: date-time
        responded_at:
          description: When the provider answered or the hold expired.
          type: string
          format: date-time
    leg-ref:
      title: LegRef
      x-profile: transfer
      description: Points at one leg of one provider's trip.
      type: object
      required:
      - provider_id
      - trip_id
      - leg_id
      properties:
        provider_id:
          description: Provider serving the leg.
          type: string
        trip_id:
          description: Requester-assigned trip id at that provider.
          type: string
        leg_id:
          description: Leg within that trip.
          type: string
    interchange:
      title: Interchange
      x-profile: transfer
      description: Links an inbound (feeder) leg to an outbound (onward) leg run by another provider at
        a transfer point, with the connection's terms (SPEC.md §10.1; semantics borrowed from NeTEx interchange).
        auto_hold_s must not exceed max_hold_s. If either leg has HAND_TO_HAND and protection is PROTECTED,
        supervision_required must be true.
      type: object
      required:
      - interchange_id
      - from
      - to
      - transfer_location
      - min_connection_s
      - protection
      properties:
        interchange_id:
          description: Broker-assigned identifier of the interchange.
          type: string
        from:
          description: The inbound leg, arriving at the transfer point.
          $ref: '#/components/schemas/leg-ref'
        to:
          description: The outbound leg, departing from the transfer point.
          $ref: '#/components/schemas/leg-ref'
        transfer_location:
          description: Where the rider changes vehicles.
          $ref: '#/components/schemas/location'
        min_connection_s:
          description: Minimum time between inbound arrival and outbound departure.
          type: integer
          minimum: 0
        max_hold_s:
          description: Longest the outbound provider can be asked to wait for a late inbound leg (§10.5).
          type: integer
          minimum: 0
          default: 0
        auto_hold_s:
          description: How long the outbound vehicle waits for a late inbound leg without being asked.
            Binds only once the outbound provider echoes it when accepting the tender.
          type: integer
          minimum: 0
          default: 0
        accessibility_buffer_s:
          description: Added to min_connection_s only when either leg has service_requirements; otherwise
            ignored.
          type: integer
          minimum: 0
          default: 0
        protection:
          description: PROTECTED connections are actively managed (heartbeats, holds); BEST_EFFORT are
            not.
          type: string
          enum:
          - PROTECTED
          - BEST_EFFORT
        supervision_required:
          description: The rider must be handed from custodian to custodian; custody records are required
            (§10.7).
          type: boolean
          default: false
        heartbeat_s:
          description: Most seconds between ETA updates the inbound provider sends while its leg is IN_PROGRESS
            toward a PROTECTED interchange; stale after twice this.
          type: integer
          minimum: 10
          default: 60
    tender-request:
      title: TenderRequest
      x-profile: transfer
      description: 'Body of `POST /tenders`: a broker offers a provider one leg of a linked journey, with
        the interchange terms, and a deadline to answer (EDI 204 pattern). On ACCEPTED the provider creates
        the trip (status CONFIRMED) under trip.trip_id, readable at /trips/{trip_id}.'
      type: object
      required:
      - tender_id
      - trip
      - interchange
      - respond_by
      properties:
        tender_id:
          description: Broker-assigned identifier, unique per requester.
          type: string
        trip:
          description: The trip being offered. Its leg must be the interchange's from or to leg for this
            provider.
          $ref: '#/components/schemas/trip-request'
        interchange:
          description: The connection this leg is part of, including the terms the provider is asked to
            commit to.
          $ref: '#/components/schemas/interchange'
        respond_by:
          description: Deadline for the provider's answer. Unanswered by then, the tender is EXPIRED.
          type: string
          format: date-time
    accepted-terms:
      title: AcceptedTerms
      x-profile: transfer
      description: Interchange terms the provider commits to by accepting a tender. Values may be lower
        than requested; the broker plans with these, not the requested ones.
      type: object
      required:
      - auto_hold_s
      - max_hold_s
      properties:
        auto_hold_s:
          description: Seconds the provider will wait for a late inbound leg without being asked.
          type: integer
          minimum: 0
        max_hold_s:
          description: Longest hold the provider will consider on request.
          type: integer
          minimum: 0
    tender:
      title: Tender
      x-profile: transfer
      description: State of a tender at the provider. OFFERED until the provider answers (synchronously
        in the POST response, or later via a TENDER_RESPONSE event), then ACCEPTED, REJECTED or, past
        respond_by, EXPIRED.
      type: object
      required:
      - tender_id
      - trip_id
      - interchange_id
      - respond_by
      - status
      - created_at
      properties:
        tender_id:
          description: Broker-assigned tender identifier.
          type: string
        trip_id:
          description: The offered trip's trip_id; on ACCEPTED, the trip exists under this id.
          type: string
        interchange_id:
          description: The interchange the leg belongs to.
          type: string
        respond_by:
          description: Deadline for the provider's answer.
          type: string
          format: date-time
        status:
          description: Tender lifecycle state.
          type: string
          enum:
          - OFFERED
          - ACCEPTED
          - REJECTED
          - EXPIRED
        reason:
          description: Why the tender was rejected.
          $ref: '#/components/schemas/refusal-reason'
        accepted_terms:
          description: Present when ACCEPTED.
          $ref: '#/components/schemas/accepted-terms'
        provider_trip_id:
          description: Provider's own id for the created trip, when ACCEPTED.
          type: string
        created_at:
          description: When the provider received the tender.
          type: string
          format: date-time
        responded_at:
          description: When the provider answered or the tender expired.
          type: string
          format: date-time
    event-types:
      title: EventTypes
      description: A set of event types a subscriber wants delivered (SPEC.md §9). Lists every event type
        in this binding. TENDER_RESPONSE through CUSTODY_HANDOFF belong to the transfer profile and are
        delivered only by providers that implement it; EXCEPTION_RAISED and EXCEPTION_RESOLVED are delivered
        by brokers to their own subscribers, never by providers.
      type: array
      minItems: 1
      uniqueItems: true
      items:
        type: string
        enum:
        - TRIP_STATUS_CHANGED
        - TRIP_SCHEDULED
        - TRIP_CANCELLED
        - TENDER_RESPONSE
        - HOLD_RESPONSE
        - MILESTONE
        - ETA_UPDATE
        - CUSTODY_HANDOFF
        - EXCEPTION_RAISED
        - EXCEPTION_RESOLVED
    subscription:
      title: Subscription
      description: A registered webhook subscription. The answer to creating it carries the signing secret;
        reading, changing or ending it (SPEC.md §9.3) never does.
      type: object
      required:
      - subscription_id
      - callback_url
      - event_types
      - created_at
      properties:
        subscription_id:
          description: Provider-assigned identifier of the subscription.
          type: string
        callback_url:
          description: HTTPS URL the provider POSTs events to.
          type: string
          format: uri
        event_types:
          description: Event types delivered to this subscription.
          $ref: '#/components/schemas/event-types'
        signing_secret:
          description: 'Shared secret for the webhook signatures (SPEC.md §12). Returned only in the response
            that creates the subscription (and its replay under the same Idempotency-Key), never by GET,
            PATCH or DELETE (SPEC.md §9.3). Providers SHOULD issue it as a Standard Webhooks secret: `whsec_`
            followed by the standard base64 of 24 to 64 random bytes. `webhook-signature` is keyed with
            those decoded bytes; the deprecated `X-TDS-Signature` is keyed with the UTF-8 bytes of the
            whole string. A secret in any other form gets `X-TDS-Signature` only.'
          type: string
          minLength: 32
        signing_secret_id:
          description: Identifier of signing_secret among the subscription's secrets (SPEC.md §12.1),
            for providers that support rotating secrets with `POST /subscriptions/{subscription_id}/secrets`.
            Use it to retire this secret early once a newer one is in use.
          type: string
          minLength: 1
        cloudevents:
          description: 'True if every delivery carries CloudEvents 1.0 binary-mode `ce-*` headers (SPEC.md
            §9.1). Absent or false: the provider may still add them, but the subscriber must not rely
            on them.'
          type: boolean
          default: false
        created_at:
          description: When the subscription was created, RFC 3339 with offset.
          type: string
          format: date-time
        updated_at:
          description: When the subscription was last changed with `PATCH /subscriptions/{subscription_id}`
            (SPEC.md §9.3), RFC 3339 with offset. Absent if it has never been changed.
          type: string
          format: date-time
    subscription-list:
      title: SubscriptionList
      description: The authenticated requester's own webhook subscriptions, oldest first (`GET /subscriptions`,
        SPEC.md §9.3). Ended subscriptions are left out and no entry carries a signing secret.
      type: object
      required:
      - subscriptions
      properties:
        subscriptions:
          description: The requester's current subscriptions, oldest first; empty if it has none.
          type: array
          items:
            $ref: '#/components/schemas/subscription'
    subscription-request:
      title: SubscriptionRequest
      description: Body of `POST /subscriptions`. Registers a webhook for trip events.
      type: object
      required:
      - callback_url
      - event_types
      properties:
        callback_url:
          description: HTTPS URL the provider POSTs events to. Plain http is allowed only for loopback
            addresses (localhost, 127.0.0.1), for development and the conformance kit's local receiver.
          type: string
          format: uri
          pattern: ^(https://|http://(localhost|127\.0\.0\.1)(:[0-9]+)?(/|$))
        event_types:
          description: Event types to deliver.
          $ref: '#/components/schemas/event-types'
        cloudevents:
          description: 'Ask for CloudEvents 1.0 binary-mode `ce-*` headers on every delivery (SPEC.md
            §9.1). The body stays the TDS event. A provider that cannot add them still creates the subscription
            and returns `cloudevents: false`.'
          type: boolean
          default: false
    subscription-update:
      title: SubscriptionUpdate
      description: Body of `PATCH /subscriptions/{subscription_id}` (SPEC.md §9.3). Each field present
        replaces the subscription's value; fields left out keep theirs. At least one field is required.
        The signing secret is not changed (rotate it with `POST /subscriptions/{subscription_id}/secrets`,
        SPEC.md §12.1).
      type: object
      properties:
        callback_url:
          description: New HTTPS URL to POST events to, with the same rules as on `POST /subscriptions`
            (plain http only for loopback addresses).
          type: string
          format: uri
          pattern: ^(https://|http://(localhost|127\.0\.0\.1)(:[0-9]+)?(/|$))
        event_types:
          description: The complete new set of event types to deliver. It replaces the current set as
            a whole, so to add one type send the current types plus the new one (read them with `GET /subscriptions/{subscription_id}`).
          $ref: '#/components/schemas/event-types'
        cloudevents:
          description: 'Ask for (true) or stop asking for (false) CloudEvents binary-mode `ce-*` headers
            (SPEC.md §9.1). As on creation, a provider that cannot add them still applies the rest of
            the change and answers `cloudevents: false`.'
          type: boolean
      anyOf:
      - required:
        - callback_url
      - required:
        - event_types
      - required:
        - cloudevents
    subscription-secret:
      title: SubscriptionSecret
      description: One signing secret of a webhook subscription (SPEC.md §12.1). While more than one is
        active the sender signs every delivery with each of them (newest first), so a receiver can switch
        secrets without losing deliveries. The secret value itself is returned only in the response that
        creates it.
      type: object
      required:
      - secret_id
      - created_at
      properties:
        secret_id:
          description: Provider-assigned identifier of the secret, unique within the subscription. Not
            secret; use it to retire the secret early.
          type: string
          minLength: 1
        signing_secret:
          description: The secret value, in the same form as the subscription's signing_secret (`whsec_`
            + standard base64 of 24 to 64 random bytes, SHOULD). Present only in the response that creates
            the secret (and in a replay of that response with the same Idempotency-Key); never in listings.
          type: string
          minLength: 32
        created_at:
          description: When the secret was created, RFC 3339 with offset.
          type: string
          format: date-time
        expires_at:
          description: When the sender stops signing with this secret, RFC 3339 with offset. Absent on
            the newest secret, which does not expire until another one replaces it. Deliveries sent at
            or after this moment carry no signature made with this secret.
          type: string
          format: date-time
    subscription-secret-list:
      title: SubscriptionSecretList
      description: 'The active signing secrets of one webhook subscription, newest first (SPEC.md §12.1):
        the answer to rotating (where the first entry is the new secret and carries its value) and to
        listing them (no values). Expired secrets are left out.'
      type: object
      required:
      - subscription_id
      - secrets
      properties:
        subscription_id:
          description: The subscription these secrets sign for.
          type: string
        secrets:
          description: Active secrets, newest first. The first has no expires_at; every other one has.
            The sender signs each delivery with all of them.
          type: array
          minItems: 1
          items:
            $ref: '#/components/schemas/subscription-secret'
    secret-rotation-request:
      title: SecretRotationRequest
      description: 'Body of `POST /subscriptions/{subscription_id}/secrets` (SPEC.md §12.1). Optional:
        an empty object or no body asks for the provider''s default overlap.'
      type: object
      properties:
        overlap_s:
          description: 'How long, in seconds from now, the subscription''s older secrets stay valid alongside
            the new one. 0 retires them at once (for a leaked secret). Absent: the provider''s default,
            which SHOULD be 86400 (24 hours). A provider MAY shorten it to its own maximum, which SHOULD
            be at least 86400; it never lengthens it, and never extends an older secret''s existing expires_at.
            The response''s expires_at values say what applies.'
          type: integer
          minimum: 0
          maximum: 2592000
    booking-window:
      title: BookingWindow
      description: 'How far ahead, and how late, a provider accepts a request for a trip (SPEC.md §7.1).
        Measured from when the provider receives the request (POST /trips, POST /tenders, or a PATCH that
        changes a window) to the trip time: the `earliest` of the leg''s pickup_window, or of its dropoff_window
        when it has none. A request is inside the window when min_notice_s <= trip time - request time
        <= max_advance_s and, if same_day is false, the trip is not on the request''s service day. Outside
        it the provider refuses with OUTSIDE_BOOKING_WINDOW, before any other refusal. Every field is
        optional; an absent field sets no limit of that kind. Rule JSON Schema can''t express (checked
        by the conformance kit): min_notice_s <= max_advance_s.'
      type: object
      properties:
        max_advance_s:
          description: Furthest ahead a trip can be requested, in seconds before the trip time (e.g. 1209600
            for 14 days). A request made earlier is refused with OUTSIDE_BOOKING_WINDOW and `bookable_from`.
          type: integer
          minimum: 0
        min_notice_s:
          description: Least notice the provider needs, in seconds before the trip time (e.g. 86400 for
            24 hours). A request made later is refused with OUTSIDE_BOOKING_WINDOW.
          type: integer
          minimum: 0
        same_day:
          description: 'Whether trips on the day the request is made are accepted. false: a trip must
            be requested by the end of the previous service day, whatever min_notice_s says (next-day
            booking, as many paratransit services require). Absent means true.'
          type: boolean
        time_zone:
          description: 'IANA time zone of the provider''s service day (e.g. America/Detroit), used to
            decide what "the same day" is. Absent: the UTC offset written in the trip''s window.'
          type: string
          pattern: ^[A-Za-z][A-Za-z0-9_+-]*(/[A-Za-z0-9_+-]+)*$
    health:
      title: Health
      description: 'Body of `GET /health`: liveness plus what the endpoint supports, how fast it usually
        decides and how far ahead it takes bookings. Requesters use typical_response_s to set deadlines
        and booking_window to plan when to submit each trip.'
      type: object
      required:
      - status
      - spec_versions
      - profiles
      - operated_via
      properties:
        status:
          description: OK when accepting requests normally; DEGRADED when up but slow or partially available.
          type: string
          enum:
          - OK
          - DEGRADED
        spec_versions:
          description: Versions of this binding the endpoint implements (SemVer).
          type: array
          minItems: 1
          items:
            type: string
            pattern: ^[0-9]+\.[0-9]+\.[0-9]+$
        profiles:
          description: Profiles implemented. CORE is always present.
          type: array
          minItems: 1
          uniqueItems: true
          contains:
            const: CORE
          items:
            type: string
            enum:
            - CORE
            - TRANSFER
            - PARATRANSIT
            - NEMT
        operated_via:
          description: DIRECT when the provider runs this endpoint itself; BROKER_PROXY when a broker
            exposes it on the provider's behalf and relays requests to staff.
          type: string
          enum:
          - DIRECT
          - BROKER_PROXY
        typical_response_s:
          description: Typical seconds from a request to an asynchronous decision. 0 or absent means decisions
            are synchronous.
          type: integer
          minimum: 0
        booking_window:
          description: 'When this provider accepts requests, relative to the trip time (SPEC.md §7.1).
            Applies to every request unless booking_window_by_profile has an entry for the request''s
            profile. Absent: the provider states no limit (it may still refuse with OUTSIDE_BOOKING_WINDOW,
            but SHOULD advertise it).'
          $ref: '#/components/schemas/booking-window'
        booking_window_by_profile:
          description: 'Booking windows that replace booking_window (not merged with it) for requests
            of one profile: `paratransit` or `nemt` when the trip request carries that object, otherwise
            `transfer` for a tender. For a provider running services with different rules, e.g. same-day
            dial-a-ride and next-day ADA paratransit.'
          type: object
          additionalProperties: false
          properties:
            paratransit:
              description: Booking window for trip requests carrying a `paratransit` object.
              $ref: '#/components/schemas/booking-window'
            nemt:
              description: Booking window for trip requests carrying an `nemt` object.
              $ref: '#/components/schemas/booking-window'
            transfer:
              description: Booking window for tenders (POST /tenders).
              $ref: '#/components/schemas/booking-window'
        secret_rotation:
          description: 'True if this endpoint implements the subscription secret operations (`POST`/`GET
            /subscriptions/{subscription_id}/secrets`, `DELETE /subscriptions/{subscription_id}/secrets/{secret_id}`,
            SPEC.md §12.1). Absent or false: a new secret is arranged out of band.'
          type: boolean
          default: false
        subscription_management:
          description: 'True if this endpoint implements reading, changing and ending webhook subscriptions
            (`GET /subscriptions`, `GET`/`PATCH`/`DELETE /subscriptions/{subscription_id}`, SPEC.md §9.3).
            Absent or false: a subscription''s event types or callback cannot be changed over the API.'
          type: boolean
          default: false
  responses:
    BadRequest:
      description: The request is malformed or fails schema validation (code INVALID_REQUEST).
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/error'
    Unauthorized:
      description: Missing or invalid credentials (code UNAUTHORIZED).
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/error'
    NotFound:
      description: No trip with this trip_id for the authenticated requester (code NOT_FOUND).
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/error'
    TooManyRequests:
      description: 'Back-pressure (SPEC.md §7.2): this client is sending too many requests (code RATE_LIMITED).
        Nothing was processed. Retry the same request with the same Idempotency-Key after the wait in
        Retry-After / retry_after_s.'
      headers:
        Retry-After:
          $ref: '#/components/headers/RetryAfter'
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/error'
    ServiceUnavailable:
      description: 'Back-pressure (SPEC.md §7.2): the provider cannot take requests for now, e.g. overload
        or maintenance (code TEMPORARILY_UNAVAILABLE). Nothing was processed. Retry the same request with
        the same Idempotency-Key after the wait in Retry-After / retry_after_s.'
      headers:
        Retry-After:
          $ref: '#/components/headers/RetryAfter'
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/error'
    SubscriptionNotFound:
      description: No subscription with this subscription_id for the authenticated requester (code NOT_FOUND).
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/error'
  headers:
    RetryAfter:
      description: 'Back-pressure hint (RFC 9110 §10.2.3; SPEC.md §7.2): how long the client should wait
        before retrying, as delta-seconds (e.g. `20`) or an HTTP-date (e.g. `Sat, 24 Oct 2026 14:05:00
        GMT`). Providers SHOULD send delta-seconds. The Problem Details body repeats the wait as `retry_after_s`.
        Clients treat it as a minimum: wait at least this long, then retry the same request with the same
        Idempotency-Key. A value that cannot be parsed is ignored.'
      schema:
        type: string
        pattern: ^([0-9]+|(Mon|Tue|Wed|Thu|Fri|Sat|Sun), [0-9]{2} (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)
          [0-9]{4} [0-9]{2}:[0-9]{2}:[0-9]{2} GMT)$
      example: '20'
