openapi: 3.1.0
info:
  title: OnPoint Delivery API
  version: 1.0.0
  summary: Create, track, and manage last-mile deliveries
  description: |
    The OnPoint Delivery API powers programmatic access to OnPoint's delivery network.
    Use it to quote delivery prices, create and dispatch orders, track driver progress,
    and receive real-time webhook notifications.

    ## Authentication

    All requests require a Bearer API key:

    ```
    Authorization: Bearer YOUR_API_KEY
    ```

    ## Conventions

    | Convention | Detail |
    |---|---|
    | Money | Integer **cents** (e.g. `899` = $8.99) |
    | Weight | **Grams** |
    | Dimensions | **Centimetres** |
    | Phone numbers | **E.164** format (`+14155551212`) |
    | Timestamps | **UTC ISO-8601** (`2026-07-26T21:15:00Z`) |

    ## Response headers

    These are returned on **every** response, success or failure:

    | Header | Purpose |
    |---|---|
    | `X-Request-Id` | Trace identifier. Log it — it is all OnPoint support needs. |
    | `RateLimit` | Remaining quota, e.g. `"default";r=17;t=1` |
    | `RateLimit-Policy` | Quota and window, e.g. `"default";q=20;w=1` |
    | `X-RateLimit-Limit` / `-Remaining` / `-Reset` | Legacy aliases for existing client code |

    Because quota is reported on every response and not only on `429`, you can throttle
    **before** being throttled. Note that `X-RateLimit-Reset` is a Unix epoch timestamp
    while `Retry-After` (sent on `429` and `503`) is a delay in seconds.

    ## Provider independence

    OnPoint fulfils deliveries through its own fleet in some markets and through
    partner carrier networks in others. **This contract does not change based on who
    carries the package.** Carrier-specific identifiers, statuses, and error strings
    are never exposed; everything is normalized into the status model below.

    Practical consequence: do not build logic that depends on which carrier serves a
    market, and do not persist any field not documented here. Use `GET /capabilities`
    to branch on what your account supports.

    ## Compatibility policy

    The major version in the base path (`/delivery/v1`) changes only for breaking
    changes. **Build a tolerant client** — the following are considered
    backward-compatible and ship without notice:

    - New optional request fields
    - New properties on response objects
    - New enum values on `DeliveryStatus`, `eventType`, and error `code`
    - New webhook event types
    - New endpoints

    So: ignore unknown properties rather than rejecting them, and handle unknown enum
    values by falling through to a default rather than throwing. A client that
    crashes on an unrecognized status will break the first time we add one.

    Breaking changes — removing a field, renaming one, tightening validation, or
    changing a status's meaning — ship as `/delivery/v2` with a minimum **12 months**
    of parallel operation on `v1`.

    ## Getting started

    1. Call `GET /capabilities` to learn which features your account supports.
    2. Optionally call `POST /quotes` for checkout pricing.
    3. Call `POST /deliveries` to create and dispatch.
    4. Register a webhook URL to receive status updates.

    Human-readable guides: [Getting started](https://docs.getonpoint.io/delivery/getting-started),
    [Webhooks](https://docs.getonpoint.io/delivery/webhooks),
    [Delivery lifecycle](https://docs.getonpoint.io/delivery/lifecycle),
    [Errors & rate limits](https://docs.getonpoint.io/delivery/errors).
  contact:
    name: OnPoint API Support
    email: api-support@getonpoint.io
    url: https://getonpoint.io/support
  license:
    name: OnPoint API Terms
    url: https://getonpoint.io/legal/api
servers:
  - url: https://api.sandbox.getonpoint.io/delivery/v1
    description: Sandbox — development and testing
  - url: https://api.getonpoint.io/delivery/v1
    description: Production — live deliveries
security:
  - bearerAuth: []
tags:
  - name: Platform
    description: Health checks and account capability discovery
  - name: Quotes
    description: Pre-dispatch pricing at checkout
  - name: Deliveries
    description: Create, read, update, and cancel deliveries
  - name: Fleet
    description: Drivers and dispatch teams
  - name: Installations
    description: |
      Military installation coverage, access policy, and pre-dispatch serviceability.

      Delivery onto a military installation is gated by the credentials of the driver,
      not by the address. These endpoints let you find out whether a destination is
      reachable — and by whom, through which gate, with what proof — before you commit
      an order, rather than discovering it at the gate.
  - name: Events
    description: |
      Retrieve past events for reconciliation after a webhook outage
  - name: Sandbox
    description: Test-only operations for driving deliveries through their lifecycle
  - name: Webhooks
    description: Outbound event notifications to your HTTPS endpoint
paths:
  /capabilities:
    get:
      tags:
        - Platform
      operationId: getCapabilities
      summary: Get account capabilities
      description: |
        Returns feature flags for your account. Call this during application startup
        to configure UI and integration paths (quotes, returns, fleet dispatch, etc.).
      responses:
        '200':
          description: Capability flags
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CapabilitiesResponse'
              examples:
                marketplaceAccount:
                  summary: Marketplace account (multi-carrier dispatch)
                  value:
                    accountType: marketplace
                    capabilities:
                      quotes: true
                      marketplaceDispatch: true
                      ownedFleetDispatch: false
                      returns: true
                      requirementEnforcement: true
                      driverLocationStreaming: true
                      routeOptimization: false
                      itemLevelPackage: true
                      cancellationFees: true
                      maxRequestsPerSecond: 20
                ownedFleetAccount:
                  summary: Owned-fleet account
                  value:
                    accountType: owned_fleet
                    capabilities:
                      quotes: false
                      marketplaceDispatch: false
                      ownedFleetDispatch: true
                      returns: false
                      requirementEnforcement: false
                      driverLocationStreaming: false
                      routeOptimization: true
                      itemLevelPackage: false
                      cancellationFees: false
                      maxRequestsPerSecond: 20
        '401':
          $ref: '#/components/responses/Unauthorized'
  /health:
    get:
      tags:
        - Platform
      operationId: getHealth
      summary: API health check
      description: |
        Returns API availability. Unauthenticated so external uptime monitors can poll it,
        and rate-limited more aggressively than authenticated endpoints for the same reason.
      security: []
      responses:
        '200':
          description: Service healthy
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HealthResponse'
              example:
                status: healthy
                checkedAt: '2026-07-26T21:00:00Z'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '503':
          description: Service degraded or unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HealthResponse'
              example:
                status: degraded
                checkedAt: '2026-07-26T21:00:00Z'
  /quotes:
    post:
      tags:
        - Quotes
      operationId: createQuote
      summary: Create a delivery quote
      description: |
        Returns priced delivery options **without** creating a delivery. Safe to call
        at checkout for orders that may not be placed.

        **No coverage is not an error.** If no carrier can price the route, this returns
        `200` with an empty `quotes` array. Check `quotes.length` before showing a
        delivery option at checkout.

        Returns **`403`** with `QUOTES_NOT_SUPPORTED` when quoting is not enabled for
        your account — check `capabilities.quotes` at startup to avoid this.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/QuoteRequest'
            examples:
              storePickupAsap:
                summary: Store pickup, ASAP dropoff
                value:
                  clientOrderReference: ORDER-48291-QUOTE
                  pickup:
                    storeReference: STORE-SF-001
                  dropoff:
                    address: 401 San Antonio Rd, Mountain View, CA 94040
                    contactName: Jane Customer
                    contactPhone: '+14155559876'
                  timeWindows:
                    deliveryMode: asap
                  package:
                    description: Prepared meal — 2 bags
                    itemsCount: 2
                    valueCents: 4599
                    weightGrams: 2500
                  verification:
                    dropoff:
                      photo:
                        level: preferred
      responses:
        '200':
          description: Quote evaluation completed. `quotes` may be empty.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/QuoteResponse'
              examples:
                priced:
                  summary: Two carriers available
                  value:
                    clientOrderReference: ORDER-48291-QUOTE
                    quotes:
                      - quoteId: qot_nh5VSwxr4trHsuKHpQbGW6
                        carrierId: car_fleet_west
                        carrierName: OnPoint West Fleet
                        currency: USD
                        totalPriceCents: 899
                        pickupEta: '2026-07-26T21:20:00Z'
                        dropoffEta: '2026-07-26T21:45:00Z'
                      - quoteId: qot_m2kL9pQx7vBnRtYu4wZcD1
                        carrierId: car_express
                        carrierName: OnPoint Express
                        currency: USD
                        totalPriceCents: 1299
                        pickupEta: '2026-07-26T21:18:00Z'
                        dropoffEta: '2026-07-26T21:35:00Z'
                    expiresAt: '2026-07-26T22:20:00Z'
                noCoverage:
                  summary: No carrier can serve this route
                  value:
                    clientOrderReference: ORDER-48291-QUOTE
                    quotes: []
                    noQuoteReason: outside_service_area
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          description: Quotes not enabled for this account
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error:
                  code: QUOTES_NOT_SUPPORTED
                  message: Delivery quotes are not enabled for this account.
                  requestId: req_01K1QUOT403
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
  /deliveries:
    get:
      tags:
        - Deliveries
      operationId: listDeliveries
      summary: List and look up deliveries
      description: |
        Returns deliveries newest first. Also the canonical way to **look up a delivery by
        your own order ID** — pass `clientOrderReference`.

        Supply either a time range (`from`) or a `clientOrderReference`. Requests with
        neither return `400`.

        Reference lookup returns an array of zero or one deliveries. An unknown reference
        is an empty array, **not** a `404` — a lookup that finds nothing is a valid answer,
        so reconciliation code does not need an error branch.
      parameters:
        - name: clientOrderReference
          in: query
          description: |
            Exact-match lookup on your order ID. Searches all history and ignores `from`
            and `to`. URL-encode the value if it contains reserved characters.
          schema:
            type: string
            maxLength: 128
          example: ORDER-48291
        - name: from
          in: query
          description: |
            Inclusive start of time range (UTC). Required unless `clientOrderReference`
            is supplied.
          schema:
            type: string
            format: date-time
          example: '2026-07-26T00:00:00Z'
        - name: to
          in: query
          description: Exclusive end of time range (UTC). Defaults to now.
          schema:
            type: string
            format: date-time
        - name: status
          in: query
          schema:
            $ref: '#/components/schemas/DeliveryStatus'
        - name: cursor
          in: query
          description: Opaque cursor from a previous response's `nextCursor`
          schema:
            type: string
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 50
      responses:
        '200':
          description: Paginated delivery list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeliveryListResponse'
              examples:
                timeRange:
                  summary: List by time range
                  value:
                    deliveries:
                      - deliveryId: del_7xKGR6PW2pBtuTeRg2b7Bw
                        clientOrderReference: ORDER-48291
                        status: delivered
                        createdAt: '2026-07-26T21:15:00Z'
                        updatedAt: '2026-07-26T21:41:30Z'
                    nextCursor: null
                referenceNotFound:
                  summary: Reference lookup with no match
                  value:
                    deliveries: []
                    nextCursor: null
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/TooManyRequests'
    post:
      tags:
        - Deliveries
      operationId: createDelivery
      summary: Create and dispatch a delivery
      description: |
        Creates a delivery and begins dispatch to an available driver.

        **Idempotency:** `Idempotency-Key` is **required**. Creating a delivery dispatches
        a real driver and incurs a real charge, so this endpoint will not accept an
        unkeyed write. See the header description for full replay semantics.

        `clientOrderReference` is a *business* identifier, not a retry token. It must be
        unique across every delivery on your account, forever — a duplicate returns `409`
        `DUPLICATE_ORDER`. To recover from a timeout without a key, look the delivery up
        with `GET /deliveries?clientOrderReference=`.

        **Initial status:** Check `status` in the response:
        - `accepted` — dispatch in progress
        - `dispatch_failed` — created but no driver yet
        - `scheduled` — waiting for scheduled window
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DeliveryCreateRequest'
            examples:
              asapWithQuote:
                summary: ASAP delivery using a prior quote
                value:
                  clientOrderReference: ORDER-48291
                  quoteId: qot_nh5VSwxr4trHsuKHpQbGW6
                  pickup:
                    storeReference: STORE-SF-001
                  dropoff:
                    address: 401 San Antonio Rd, Mountain View, CA 94040
                    contactName: Jane Customer
                    contactPhone: '+14155559876'
                    instructions: Ring doorbell twice
                  timeWindows:
                    deliveryMode: asap
                  package:
                    description: Prepared meal — 2 bags
                    itemsCount: 2
                    valueCents: 4599
                    weightGrams: 2500
                  verification:
                    dropoff:
                      photo:
                        level: preferred
                        substituteWhenProhibited: signature
                  handling:
                    handoff: hand_to_recipient
                    undeliverableAction: return_to_pickup
                  tipAmountCents: 300
                  metadata:
                    source: mobile_app
              scheduledOwnedFleet:
                summary: Scheduled delivery with team dispatch
                value:
                  clientOrderReference: ORDER-48305
                  pickup:
                    storeReference: STORE-SF-001
                  dropoff:
                    address:
                      line1: 185 University Ave
                      city: Palo Alto
                      state: CA
                      postalCode: '94301'
                    contactName: Sam Chen
                    contactPhone: '+14155558765'
                  timeWindows:
                    deliveryMode: scheduled
                    pickup:
                      start: '2026-07-27T17:00:00Z'
                      end: '2026-07-27T17:30:00Z'
                    dropoff:
                      start: '2026-07-27T17:30:00Z'
                      end: '2026-07-27T19:00:00Z'
                  package:
                    description: Catering — 4 boxes
                    itemsCount: 4
                    valueCents: 12500
                  dispatch:
                    mode: team_queue
                    teamReference: TEAM-PENINSULA
              onInstallation:
                summary: Commissary order to barracks on a military installation
                description: |
                  Delivery to a room inside Fort Liberty. Supplying `installation.code`
                  restricts dispatch to drivers holding a current credential for that
                  installation; `unit` gives the driver a dispatchable building and room
                  instead of a packed instruction string; `cq_desk` names the staffed desk
                  that is the only lawful handoff point in most barracks.

                  Note what is **not** requested: photo proof. Fort Liberty prohibits
                  photography, so a photo request would be suppressed anyway — this order
                  asks for a PIN readback instead. Alcohol is declared in
                  `package.contains`, which raises an identity check automatically.
                value:
                  clientOrderReference: ORDER-51002
                  pickup:
                    storeReference: STORE-COMMISSARY-LIBERTY
                  dropoff:
                    address: 2175 Reilly Rd, Fort Liberty, NC 28310
                    contactName: SPC Dana Whitfield
                    contactPhone: '+19105554412'
                    installation:
                      code: INST-FTLIBERTY
                      gate: All American Gate
                    unit:
                      buildingNumber: C-3421
                      room: 214B
                      unitDesignation: 2-505 PIR
                      deliveryPoint: cq_desk
                  timeWindows:
                    deliveryMode: scheduled
                    dropoff:
                      start: '2026-07-27T18:00:00Z'
                      end: '2026-07-27T20:00:00Z'
                  package:
                    description: Commissary grocery order
                    itemsCount: 14
                    valueCents: 8740
                    contains:
                      - alcohol
                      - perishable_cold_chain
                  verification:
                    dropoff:
                      pinCode:
                        level: required
                        source: onpoint_generated
                      identity:
                        level: required
                        minimumAge: 21
                  handling:
                    handoff: cq_desk
                    undeliverableAction: fallback_handoff
                    fallbackHandoff: visitor_center
      responses:
        '200':
          description: |
            Replay of an earlier create with the same `Idempotency-Key` and body.
            No new delivery was created.
          headers:
            Idempotency-Replayed:
              $ref: '#/components/headers/IdempotencyReplayed'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeliveryResponse'
        '201':
          description: Delivery created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeliveryResponse'
              examples:
                accepted:
                  summary: Dispatch in progress
                  value:
                    deliveryId: del_7xKGR6PW2pBtuTeRg2b7Bw
                    clientOrderReference: ORDER-48291
                    status: accepted
                    pickup:
                      storeReference: STORE-SF-001
                      businessName: OnPoint Kitchen — SF
                    dropoff:
                      address: 401 San Antonio Rd, Mountain View, CA 94040
                      contactName: Jane Customer
                      contactPhone: '+14155559876'
                    pricing:
                      currency: USD
                      deliveryFeeCents: 899
                      tipAmountCents: 300
                      totalPriceCents: 1199
                    trackingUrl: https://track.getonpoint.io/d/del_7xKGR6PW2pBtuTeRg2b7Bw
                    createdAt: '2026-07-26T21:15:00Z'
                    updatedAt: '2026-07-26T21:15:02Z'
                dispatchFailed:
                  summary: Created but no driver assigned
                  value:
                    deliveryId: del_8yLHS7QX3qCuvUfSh3c8Cx
                    clientOrderReference: ORDER-48292
                    status: dispatch_failed
                    statusReason: No drivers available in service area
                    createdAt: '2026-07-26T21:16:00Z'
                    updatedAt: '2026-07-26T21:16:05Z'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '409':
          $ref: '#/components/responses/Conflict'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
  /deliveries/{deliveryId}:
    get:
      tags:
        - Deliveries
      operationId: getDelivery
      summary: Get delivery by ID
      description: |
        Returns the full current snapshot of a delivery, including `statusHistory`.

        Use this for reconciliation, not for tracking — polling for status changes
        will hit rate limits well before it gives you the latency webhooks do.
      parameters:
        - $ref: '#/components/parameters/DeliveryId'
      responses:
        '200':
          description: Delivery found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeliveryResponse'
              example:
                deliveryId: del_7xKGR6PW2pBtuTeRg2b7Bw
                clientOrderReference: ORDER-48291
                status: delivered
                driver:
                  id: drv_a1b2c3d4
                  name: Alex Rivera
                  phoneNumber: '+14155550123'
                proofOfDelivery:
                  - type: photo
                    stage: dropoff
                    url: https://cdn.getonpoint.io/pod/example.jpg
                    capturedAt: '2026-07-26T21:41:30Z'
                trackingUrl: https://track.getonpoint.io/d/del_7xKGR6PW2pBtuTeRg2b7Bw
                createdAt: '2026-07-26T21:15:00Z'
                updatedAt: '2026-07-26T21:41:30Z'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
    patch:
      tags:
        - Deliveries
      operationId: updateDelivery
      summary: Update a delivery
      description: |
        Partial update — only supplied fields change.

        **Merge semantics:** Top-level and nested objects are **shallow-merged**. Supplying
        `dropoff.instructions` leaves every other `dropoff` field untouched. Supplying an
        array (`package.contains`, `package.items`, `verification.dropoff.barcode.values`)
        **replaces** it entirely. Send `null` to clear an optional scalar.

        **Before `pickup_complete`:** all create fields may be updated.
        **After `pickup_complete`:** only `dropoff.instructions`, `dropoff.contactPhone`,
        and `tipAmountCents`.

        **Verification cannot be relaxed after dispatch.** Raising a level or adding a
        mechanism is accepted while the delivery is pre-pickup; lowering one that cargo
        attributes or destination policy require returns `422`
        `UNSUPPORTED_REQUIREMENT`. A merchant cannot remove the age check from an
        alcohol order mid-flight.
        **After `delivered`:** returns `409`.
      parameters:
        - $ref: '#/components/parameters/DeliveryId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DeliveryUpdateRequest'
            examples:
              instructionsOnly:
                summary: Change driver instructions after pickup
                value:
                  dropoff:
                    instructions: Updated — use side gate
                  tipAmountCents: 500
              rescheduleWindow:
                summary: Move the scheduled dropoff window
                value:
                  timeWindows:
                    deliveryMode: scheduled
                    dropoff:
                      start: '2026-07-27T19:00:00Z'
                      end: '2026-07-27T20:30:00Z'
      responses:
        '200':
          description: Delivery updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeliveryResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '429':
          $ref: '#/components/responses/TooManyRequests'
  /deliveries/{deliveryId}/cancel:
    post:
      tags:
        - Deliveries
      operationId: cancelDelivery
      summary: Cancel a delivery
      description: |
        Cancels an in-flight or scheduled delivery. Returns `409` after terminal states.

        Cancellation fees (when supported) appear in `pricing.cancellationFeeCents`.
        The reason is recorded for reporting and is not forwarded to the driver app.

        **Idempotency:** Because cancelling can incur a fee, supply `Idempotency-Key`
        so a retried cancel after a network timeout cannot be double-charged.
      parameters:
        - $ref: '#/components/parameters/DeliveryId'
        - $ref: '#/components/parameters/IdempotencyKeyOptional'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CancelRequest'
            example:
              reason: customer_cancelled
              reasonNotes: Customer changed delivery address
      responses:
        '200':
          description: Delivery cancelled
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeliveryResponse'
              example:
                deliveryId: del_7xKGR6PW2pBtuTeRg2b7Bw
                clientOrderReference: ORDER-48291
                status: cancelled
                statusReason: Cancelled by merchant before pickup
                pricing:
                  currency: USD
                  deliveryFeeCents: 0
                  tipAmountCents: 0
                  cancellationFeeCents: 500
                  totalPriceCents: 500
                updatedAt: '2026-07-26T21:20:00Z'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '429':
          $ref: '#/components/responses/TooManyRequests'
  /drivers:
    get:
      tags:
        - Fleet
      operationId: listDrivers
      summary: List drivers
      description: |
        Returns drivers in your fleet roster. Availability depends on account type —
        marketplace accounts may return only drivers currently assigned to active deliveries.
      parameters:
        - name: teamId
          in: query
          schema:
            type: string
        - name: onDuty
          in: query
          schema:
            type: boolean
      responses:
        '200':
          description: Driver list
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/DriverProfile'
              example:
                - id: drv_a1b2c3d4
                  name: Alex Rivera
                  phoneNumber: '+14155550123'
                  onDuty: true
                  status: active
                  teams:
                    - TEAM-PENINSULA
                  vehicleDescription: White Honda Civic
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/TooManyRequests'
  /drivers/{driverId}:
    get:
      tags:
        - Fleet
      operationId: getDriver
      summary: Get driver
      description: |
        Returns a single driver profile. Owned-fleet accounts only — marketplace
        accounts do not have visibility into carrier driver rosters.
      parameters:
        - name: driverId
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Driver profile
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DriverProfile'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
  /teams:
    get:
      tags:
        - Fleet
      operationId: listTeams
      summary: List dispatch teams
      description: |
        Dispatch teams available to your account.

        **Dispatch-partner surface.** Teams exist only for accounts that direct dispatch
        themselves — platform partners and the OnPoint dashboard. A standard merchant
        account does not choose a team: OnPoint recruits drivers and assigns them by
        market, and this endpoint returns an empty array. Check
        `capabilities.ownedFleetDispatch` before building against it.
      responses:
        '200':
          description: Team list
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Team'
              example:
                - id: TEAM-PENINSULA
                  name: Peninsula Dispatch
                  workerCount: 12
                  hubDestinationId: dest_hub_peninsula
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/TooManyRequests'
  /installations:
    get:
      tags:
        - Installations
      operationId: listInstallations
      summary: List served installations
      description: |
        Every military installation OnPoint serves, with current access level and
        delivery posture.

        Use this to answer "do you deliver to Fort Liberty?" at onboarding time. For a
        specific order, prefer `POST /serviceability`, which also accounts for the
        cargo, the time, and whether a credentialed driver is actually available now.

        The list is stable enough to cache for a day. Access **policy** is not — see
        `fpconLevel` and the `installation.access_policy_changed` webhook.
      parameters:
        - name: state
          in: query
          description: Filter to a two-letter US state or territory code.
          schema:
            type: string
            pattern: ^[A-Z]{2}$
        - name: branch
          in: query
          schema:
            type: string
            enum:
              - army
              - navy
              - air_force
              - marine_corps
              - space_force
              - coast_guard
              - joint
              - dla
        - name: acceptingDeliveries
          in: query
          description: Filter to installations currently open to commercial delivery.
          schema:
            type: boolean
        - $ref: '#/components/parameters/Cursor'
        - $ref: '#/components/parameters/Limit'
      responses:
        '200':
          description: Installation list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InstallationListResponse'
              example:
                installations:
                  - code: INST-FTLIBERTY
                    name: Fort Liberty
                    branch: army
                    state: NC
                    acceptingDeliveries: true
                    accessLevel: id_required
                    photographyPermitted: false
                    supportedDeliveryPoints:
                      - cq_desk
                      - mailroom
                      - gate_handoff
                nextCursor: null
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/TooManyRequests'
  /installations/{installationCode}:
    get:
      tags:
        - Installations
      operationId: getInstallation
      summary: Get installation detail
      description: |
        Full access policy for one installation: gates and their hours, permitted proof
        types, supported delivery points, prohibited cargo, and current posture.

        Read this before building installation-specific behavior into your checkout. In
        particular, check `photographyPermitted` — if you are relying on photo proof of
        delivery and the installation forbids it, you want to know at design time rather
        than from a suppressed-verification notice on your first order.
      parameters:
        - name: installationCode
          in: path
          required: true
          schema:
            type: string
          examples:
            liberty:
              value: INST-FTLIBERTY
      responses:
        '200':
          description: Installation detail
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Installation'
              example:
                code: INST-FTLIBERTY
                name: Fort Liberty
                branch: army
                state: NC
                acceptingDeliveries: true
                accessLevel: id_required
                fpconLevel: normal
                photographyPermitted: false
                imagingRestrictions: no_recording_devices
                supportedDeliveryPoints:
                  - cq_desk
                  - mailroom
                  - gate_handoff
                permittedProofTypes:
                  - signature
                  - pin_code
                  - barcode
                prohibitedCargo:
                  - alcohol
                  - controlled_cargo
                gates:
                  - name: All American Gate
                    commercialVehiclesPermitted: true
                    hours: Mon-Fri 05:00-22:00; Sat-Sun 06:00-20:00
                    timezone: America/New_York
                policyUpdatedAt: '2026-07-20T14:00:00Z'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
  /serviceability:
    post:
      tags:
        - Installations
      operationId: checkServiceability
      summary: Check whether a stop can be served right now
      description: |
        Answers a question no pricing call can: **can this be delivered, by whom, through
        which gate, with what proof, right now** — and if not, precisely why.

        This is deliberately separate from `POST /quotes`. A quote tells you the price if
        the delivery is possible; serviceability tells you whether it is possible at all,
        costs nothing, and returns a structured reason on failure. Call it at address
        entry so a service member is told "this installation is closed to commercial
        delivery until 05:00" while they can still act on it, rather than after checkout.

        The credential check is the part that matters. A publicly documented failure mode
        on competing platforms is a customer ordering three or four times before randomly
        drawing a courier who happens to hold base access, because nothing in the flow
        knows the requirement exists. Here it is checked before the order is placed.

        Serviceability is a point-in-time answer. Force protection posture can change with
        under 24 hours' notice, so do not cache it across a scheduled delivery window —
        subscribe to `installation.access_policy_changed` instead.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ServiceabilityRequest'
            example:
              dropoff:
                address: 2175 Reilly Rd, Fort Liberty, NC 28310
                installation:
                  code: INST-FTLIBERTY
                unit:
                  buildingNumber: C-3421
                  room: 214B
                  deliveryPoint: cq_desk
              package:
                contains:
                  - perishable_cold_chain
              deliverAt: '2026-07-27T18:00:00Z'
      responses:
        '200':
          description: |
            Serviceability evaluated. A `serviceable: false` result is a **successful**
            evaluation, not an error — check the field, not the status code.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ServiceabilityResponse'
              examples:
                serviceable:
                  summary: Deliverable, with photo proof suppressed
                  value:
                    serviceable: true
                    installation:
                      code: INST-FTLIBERTY
                      name: Fort Liberty
                      accessLevel: id_required
                      fpconLevel: normal
                    recommendedGate: All American Gate
                    credentialedDriversAvailable: true
                    permittedProofTypes:
                      - signature
                      - pin_code
                      - barcode
                    suppressedProofTypes:
                      - photo
                    supportedDeliveryPoints:
                      - cq_desk
                      - mailroom
                      - gate_handoff
                    evaluatedAt: '2026-07-26T22:45:00Z'
                gateClosed:
                  summary: Not deliverable at the requested time
                  value:
                    serviceable: false
                    reasonCode: gate_closed
                    reason: All commercial gates at Fort Liberty are closed until 05:00 local.
                    installation:
                      code: INST-FTLIBERTY
                      name: Fort Liberty
                      accessLevel: id_required
                    retryAfter: '2026-07-28T09:00:00Z'
                    evaluatedAt: '2026-07-26T22:45:00Z'
                overseas:
                  summary: APO address — no commercial carrier can serve it
                  value:
                    serviceable: false
                    reasonCode: unsupported_destination_type
                    reason: APO/FPO/DPO addresses are served only by USPS. No commercial carrier, including OnPoint, can deliver to them.
                    evaluatedAt: '2026-07-26T22:45:00Z'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/TooManyRequests'
  /events:
    get:
      tags:
        - Events
      operationId: listEvents
      summary: Retrieve past events
      description: |
        Every event OnPoint has emitted for your account, retained for **30 days** and
        queryable whether or not it was successfully delivered to your webhook endpoint.

        This is the recovery path when your endpoint was down, misconfigured, or behind a
        bad deploy. Filter with `delivered=false` to get exactly the events you missed,
        replay them through the same handler, and you are caught up — no support ticket,
        no reconstructing state by walking every delivery.

        ```
        GET /events?delivered=false&since=2026-07-26T00:00:00Z
        ```

        Events are returned oldest-first so you can apply them in order. Your handler
        should still be idempotent on `eventId`: an event can appear here **and** arrive
        by webhook, since a delivery attempt may have succeeded after you queried.
      parameters:
        - name: since
          in: query
          description: Inclusive lower bound on `occurredAt`. Defaults to 24 hours ago. Max age 30 days.
          schema:
            type: string
            format: date-time
        - name: until
          in: query
          description: Exclusive upper bound on `occurredAt`. Defaults to now.
          schema:
            type: string
            format: date-time
        - name: delivered
          in: query
          description: |
            `false` returns only events that have not been successfully delivered to your
            endpoint — the backfill set after an outage. Omit for all events.
          schema:
            type: boolean
        - name: deliveryId
          in: query
          description: Restrict to events for one delivery.
          schema:
            type: string
        - name: eventType
          in: query
          description: Repeatable. Restrict to specific event types.
          schema:
            type: array
            items:
              type: string
          style: form
          explode: true
        - $ref: '#/components/parameters/Cursor'
        - $ref: '#/components/parameters/Limit'
      responses:
        '200':
          description: Event list, oldest first
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EventListResponse'
              example:
                events:
                  - eventId: evt_01K1EVT7Q2
                    eventType: delivery.delivered
                    occurredAt: '2026-07-26T21:14:32Z'
                    livemode: true
                    deliveryId: del_7xKGR6PW2pBtuTeRg2b7Bw
                    deliveryAttempts: 3
                    lastDeliveryStatus: failed
                    lastAttemptAt: '2026-07-26T21:20:11Z'
                    data:
                      deliveryId: del_7xKGR6PW2pBtuTeRg2b7Bw
                      clientOrderReference: ORDER-48291
                      status: delivered
                nextCursor: null
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/TooManyRequests'
  /deliveries/{deliveryId}/simulate:
    post:
      tags:
        - Sandbox
      operationId: simulateDelivery
      summary: Advance a sandbox delivery
      description: |
        Drive a sandbox delivery to a chosen status and emit the real webhooks for every
        intervening transition, signed with your real signing secret.

        **Sandbox keys only.** A live key returns `403` `SANDBOX_ONLY`.

        This exists so your integration can be tested in CI rather than by asking OnPoint
        to poke states by hand. Signing simulated webhooks identically to production is
        deliberate: signature verification is the most error-prone part of a webhook
        integration, and a simulator that skips it leaves the riskiest code untested.

        Transitions are validated against the real state machine. Asking a `delivered`
        delivery to go back to `driver_at_pickup` returns `422`, exactly as production
        would refuse it.

        Unhappy paths are first-class. Pair `advanceTo: failed` with a
        `statusReasonCode` such as `denied_at_gate` to exercise the branch that matters
        most on installation deliveries and is hardest to reproduce for real.
      parameters:
        - $ref: '#/components/parameters/DeliveryId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SimulateRequest'
            examples:
              happyPath:
                summary: Drive through to delivered
                value:
                  advanceTo: delivered
              gateDenial:
                summary: Fail at the gate
                value:
                  advanceTo: failed
                  statusReasonCode: denied_at_gate
              stepwise:
                summary: One transition, no intermediate events
                value:
                  advanceTo: driver_at_pickup
                  emitIntermediateEvents: false
      responses:
        '200':
          description: Delivery advanced. Webhooks for each emitted transition follow asynchronously.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SimulateResponse'
              example:
                deliveryId: del_7xKGR6PW2pBtuTeRg2b7Bw
                livemode: false
                status: delivered
                emittedEvents:
                  - delivery.driver_enroute_to_dropoff
                  - delivery.driver_at_dropoff
                  - delivery.delivered
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          description: Simulation attempted with a live API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error:
                  code: SANDBOX_ONLY
                  message: Simulation is only available with a sandbox API key.
                  requestId: req_01K1SIM403
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '429':
          $ref: '#/components/responses/TooManyRequests'
webhooks:
  deliveryEvent:
    post:
      tags:
        - Webhooks
      operationId: onDeliveryEvent
      summary: Delivery event
      description: |
        OnPoint POSTs delivery lifecycle events to your registered HTTPS URL.

        Signing follows the [Standard Webhooks](https://www.standardwebhooks.com/)
        specification, so any off-the-shelf verification library will work.

        **Headers:** `webhook-id`, `webhook-timestamp`, `webhook-signature`
        **Verify:** `HMAC-SHA256(secret, "{webhook-id}.{webhook-timestamp}.{rawBody}")`, base64.
        **Replay:** Reject when `webhook-timestamp` is more than 5 minutes from your clock.
        **Dedupe:** Process each `eventId` at most once.
        **Retries:** ~5s, 5m, 30m, 2h, 5h, 10h, 10h on non-2xx.

        See [Webhook guide](https://docs.getonpoint.io/delivery/webhooks) for full event catalog.
      parameters:
        - $ref: '#/components/parameters/WebhookId'
        - $ref: '#/components/parameters/WebhookTimestamp'
        - $ref: '#/components/parameters/WebhookSignature'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DeliveryWebhook'
            examples:
              driverAssigned:
                summary: Driver assigned
                value:
                  eventId: evt_01K1DRIVERASSIGN
                  eventType: delivery.driver_assigned
                  occurredAt: '2026-07-26T21:18:04Z'
                  previousStatus: accepted
                  delivery:
                    deliveryId: del_7xKGR6PW2pBtuTeRg2b7Bw
                    clientOrderReference: ORDER-48291
                    status: driver_assigned
                    driver:
                      id: drv_a1b2c3d4
                      name: Alex Rivera
                      phoneNumber: '+14155550123'
                    trackingUrl: https://track.getonpoint.io/d/del_7xKGR6PW2pBtuTeRg2b7Bw
              locationUpdated:
                summary: Driver location updated (no status change)
                value:
                  eventId: evt_01K1LOCATION01
                  eventType: delivery.driver_location_updated
                  occurredAt: '2026-07-26T21:28:15Z'
                  delivery:
                    deliveryId: del_7xKGR6PW2pBtuTeRg2b7Bw
                    clientOrderReference: ORDER-48291
                    status: driver_enroute_to_dropoff
                    driver:
                      id: drv_a1b2c3d4
                      location:
                        latitude: 37.3988
                        longitude: -122.1085
                        updatedAt: '2026-07-26T21:28:15Z'
              delivered:
                summary: Delivery completed
                value:
                  eventId: evt_01K1DELIVERED01
                  eventType: delivery.delivered
                  occurredAt: '2026-07-26T21:41:30Z'
                  previousStatus: driver_at_dropoff
                  delivery:
                    deliveryId: del_7xKGR6PW2pBtuTeRg2b7Bw
                    clientOrderReference: ORDER-48291
                    status: delivered
                    proofOfDelivery:
                      - type: photo
                        stage: dropoff
                        url: https://cdn.getonpoint.io/pod/example.jpg
                        capturedAt: '2026-07-26T21:41:30Z'
      responses:
        '200':
          description: Event accepted
  driverEvent:
    post:
      tags:
        - Webhooks
      operationId: onDriverEvent
      summary: Driver roster event
      description: |
        Driver lifecycle events for owned-fleet accounts. Same signature and retry
        semantics as delivery events.
      parameters:
        - $ref: '#/components/parameters/WebhookId'
        - $ref: '#/components/parameters/WebhookTimestamp'
        - $ref: '#/components/parameters/WebhookSignature'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DriverWebhook'
            example:
              eventId: evt_01K1DUTYCHANGE
              eventType: driver.duty_changed
              occurredAt: '2026-07-26T14:00:00Z'
              driver:
                id: drv_a1b2c3d4
                name: Alex Rivera
                onDuty: true
      responses:
        '200':
          description: Event accepted
  installationEvent:
    post:
      tags:
        - Webhooks
      operationId: onInstallationEvent
      summary: Installation access policy changed
      description: |
        Fired when the access policy at an installation you deliver to changes: force
        protection posture, gate hours, commercial-delivery suspension, or permitted
        proof types.

        **Subscribe to this rather than polling `GET /installations`.** Posture changes
        propagate with very little notice — there are documented cases of an installation
        tightening access overnight and turning away roughly 150 delivery drivers over a
        single weekend because none of the platforms carrying those orders knew. If you
        hold scheduled orders, this event is your cue to re-run `POST /serviceability`
        against them and notify affected customers before the driver is at the gate.

        Affected in-flight deliveries also emit their own `delivery.*` events; this one
        tells you about the installation, not any single order.

        Same signature and retry semantics as delivery events.
      parameters:
        - $ref: '#/components/parameters/WebhookId'
        - $ref: '#/components/parameters/WebhookTimestamp'
        - $ref: '#/components/parameters/WebhookSignature'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/InstallationWebhook'
            examples:
              fpconRaised:
                summary: Force protection posture raised
                value:
                  eventId: evt_01K1FPCON01
                  eventType: installation.access_policy_changed
                  occurredAt: '2026-07-27T04:12:00Z'
                  livemode: true
                  changed:
                    - fpconLevel
                    - accessLevel
                  affectedDeliveryCount: 3
                  installation:
                    code: INST-FTMEADE
                    name: Fort Meade
                    branch: army
                    state: MD
                    acceptingDeliveries: true
                    accessLevel: escort_required
                    fpconLevel: bravo
                    photographyPermitted: false
                    policyUpdatedAt: '2026-07-27T04:12:00Z'
              deliverySuspended:
                summary: Commercial delivery suspended
                value:
                  eventId: evt_01K1SUSPEND01
                  eventType: installation.access_policy_changed
                  occurredAt: '2026-07-27T05:00:00Z'
                  livemode: true
                  changed:
                    - acceptingDeliveries
                  affectedDeliveryCount: 11
                  installation:
                    code: INST-FTMEADE
                    name: Fort Meade
                    acceptingDeliveries: false
                    accessLevel: restricted
      responses:
        '200':
          description: Event accepted
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: |
        API key issued per merchant and environment. Include in every request:

        `Authorization: Bearer YOUR_API_KEY`
  parameters:
    DeliveryId:
      name: deliveryId
      in: path
      required: true
      description: OnPoint delivery identifier returned at creation
      schema:
        type: string
      example: del_7xKGR6PW2pBtuTeRg2b7Bw
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: true
      description: |
        Client-generated key that makes this write safe to retry. Use a UUID or any
        unique string — one key per logical operation, reused only for retries of that
        same operation.

        | Situation | OnPoint response |
        |---|---|
        | First request with this key | Executed normally (`201` on create) |
        | Retry with the **same** key and the **same** body | Original response replayed, with `Idempotency-Replayed: true`. Create replays as `200`, not `201`. |
        | Retry with the same key and a **different** body | `409` `IDEMPOTENCY_KEY_REUSED` — nothing is executed |
        | Retry while the first request is still in flight | `409` `IDEMPOTENCY_CONFLICT` — retry after a short delay |

        Keys are scoped to your API key and retained for **24 hours**. After that the key
        is forgotten and a replay would create a new delivery — so reconcile with
        `GET /deliveries?clientOrderReference=` rather than retrying beyond 24 hours.
      schema:
        type: string
        maxLength: 128
      example: 6f1b0c8e-7f2a-4a2f-9d0e-1c9a5b3e7d41
    Cursor:
      name: cursor
      in: query
      description: Opaque cursor from a previous response's `nextCursor`
      schema:
        type: string
    Limit:
      name: limit
      in: query
      description: Page size. `nextCursor` is `null` on the final page.
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 50
    IdempotencyKeyOptional:
      name: Idempotency-Key
      in: header
      required: false
      description: |
        Strongly recommended. Same semantics as on `POST /deliveries` — see that
        endpoint for the full replay table.
      schema:
        type: string
        maxLength: 128
    WebhookId:
      name: webhook-id
      in: header
      required: true
      description: |
        Unique identifier for this event. Identical to `eventId` in the body.
        Use it as your deduplication key.
      schema:
        type: string
      example: evt_01K1DRIVERASSIGN
    WebhookTimestamp:
      name: webhook-timestamp
      in: header
      required: true
      description: |
        Unix timestamp in **seconds** at which the event was sent. Reject requests more
        than 5 minutes from your own clock in either direction.
      schema:
        type: string
      example: '1785102284'
    WebhookSignature:
      name: webhook-signature
      in: header
      required: true
      description: |
        Space-separated list of versioned signatures, each `v1,<base64>`.

        Each signature is `HMAC-SHA256(secret, "{webhook-id}.{webhook-timestamp}.{rawBody}")`,
        base64-encoded. **The id and timestamp are inside the signed payload**, so a captured
        event cannot be replayed with a rewritten timestamp.

        More than one signature is present during secret rotation — accept the request if
        **any** signature matches **any** of your active secrets.
      schema:
        type: string
      example: v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE=
  headers:
    RetryAfter:
      description: Seconds to wait before retrying
      schema:
        type: integer
    IdempotencyReplayed:
      description: |
        `true` when this response was replayed from a previous request with the same
        `Idempotency-Key` rather than newly executed. Absent on first execution.

        Note for merchants porting from Stripe: their equivalent header is spelled
        `Idempotent-Replayed`. Ours matches our own `Idempotency-Key`.
      schema:
        type: boolean
    RequestId:
      description: |
        Unique identifier for this request, present on **every** response including
        successes. Log it. It is the only thing OnPoint support needs to trace a call,
        and it matches `error.requestId` on failures.
      schema:
        type: string
      example: req_01K1ABC123
    RateLimit:
      description: |
        Remaining quota under the applicable policy, per
        [`draft-ietf-httpapi-ratelimit-headers`](https://datatracker.ietf.org/doc/draft-ietf-httpapi-ratelimit-headers/).
        `r` is requests remaining, `t` is seconds until the window resets.

        Sent on **every** response, not just `429`, so you can throttle before you are
        throttled.
      schema:
        type: string
      example: '"default";r=17;t=1'
    RateLimitPolicy:
      description: |
        The quota policies in force. `q` is the quota, `w` the window in seconds.
        Stable across responses — read it once at startup rather than per request.
      schema:
        type: string
      example: '"default";q=20;w=1'
    XRateLimitLimit:
      description: |
        Requests permitted in the current window. Legacy alias emitted alongside
        `RateLimit-Policy` for the large body of existing client code that parses the
        `X-RateLimit-*` convention. Prefer the standard headers in new code.
      schema:
        type: integer
      example: 20
    XRateLimitRemaining:
      description: Requests remaining in the current window. Legacy alias for `RateLimit`'s `r`.
      schema:
        type: integer
      example: 17
    XRateLimitReset:
      description: |
        **Unix epoch seconds** at which the window resets — an absolute timestamp, not a
        delay. Subtract your own clock to get a delay. (This is the GitHub convention and
        a well-known source of client bugs; `Retry-After` on a `429` is a delay in
        seconds, and the two are not interchangeable.)
      schema:
        type: integer
      example: 1785102285
  responses:
    BadRequest:
      description: Invalid request
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              code: VALIDATION_ERROR
              message: Request body failed validation.
              requestId: req_01K1BAD400
    Unauthorized:
      description: Missing or invalid API key
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              code: UNAUTHORIZED
              message: Invalid or missing API key.
              requestId: req_01K1AUTH401
    NotFound:
      description: Resource not found
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              code: DELIVERY_NOT_FOUND
              message: No delivery found with the given identifier.
              requestId: req_01K1NF404
    Conflict:
      description: Operation not allowed in current state
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          examples:
            invalidTransition:
              summary: Terminal state reached
              value:
                error:
                  code: INVALID_STATE_TRANSITION
                  message: Cannot update delivery after it has been delivered.
                  requestId: req_01K1CONF409
            duplicateOrder:
              summary: Client order reference already used
              value:
                error:
                  code: DUPLICATE_ORDER
                  message: A delivery already exists with clientOrderReference ORDER-48291.
                  details:
                    deliveryId: del_7xKGR6PW2pBtuTeRg2b7Bw
                  requestId: req_01K1DUP409
            idempotencyInFlight:
              summary: Earlier request with this key still processing
              value:
                error:
                  code: IDEMPOTENCY_CONFLICT
                  message: A request with this Idempotency-Key is still in progress. Retry shortly.
                  requestId: req_01K1IDEM409
            idempotencyKeyReused:
              summary: Same key, different body
              value:
                error:
                  code: IDEMPOTENCY_KEY_REUSED
                  message: This Idempotency-Key was already used with a different request body.
                  requestId: req_01K1IDEM409R
    UnprocessableEntity:
      description: |
        The request is well-formed but was rejected by a business rule. Compare with
        `400`, which means the request could not be parsed or failed schema validation.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          examples:
            unsupportedRequirement:
              summary: Requirement not enabled for this account
              value:
                error:
                  code: UNSUPPORTED_REQUIREMENT
                  message: Identity verification is not enabled for this account.
                  details:
                    fieldErrors:
                      - field: verification.dropoff.identity.level
                        code: UNSUPPORTED_REQUIREMENT
                        message: Not enabled. Contact OnPoint support to enable.
                  requestId: req_01K1REQ422
            unknownStore:
              summary: Unknown store reference
              value:
                error:
                  code: INVALID_STORE_REFERENCE
                  message: No store is registered with reference STORE-SF-999.
                  details:
                    fieldErrors:
                      - field: pickup.storeReference
                        code: INVALID_STORE_REFERENCE
                        message: Unknown store reference.
                  requestId: req_01K1STORE422
    TooManyRequests:
      description: |
        Rate limit exceeded. Honor `Retry-After`, which is a **delay in seconds** —
        not the epoch timestamp carried by `X-RateLimit-Reset`.
      headers:
        Retry-After:
          $ref: '#/components/headers/RetryAfter'
        X-Request-Id:
          $ref: '#/components/headers/RequestId'
        RateLimit:
          $ref: '#/components/headers/RateLimit'
        RateLimit-Policy:
          $ref: '#/components/headers/RateLimitPolicy'
        X-RateLimit-Limit:
          $ref: '#/components/headers/XRateLimitLimit'
        X-RateLimit-Remaining:
          $ref: '#/components/headers/XRateLimitRemaining'
        X-RateLimit-Reset:
          $ref: '#/components/headers/XRateLimitReset'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              code: RATE_LIMIT_EXCEEDED
              message: Rate limit exceeded. Retry after 2 seconds.
              requestId: req_01K1RATE429
    ServiceUnavailable:
      description: Temporary service outage
      headers:
        Retry-After:
          $ref: '#/components/headers/RetryAfter'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
  schemas:
    AccountType:
      type: string
      enum:
        - marketplace
        - owned_fleet
        - hybrid
      description: |
        - `marketplace` — multi-carrier dispatch with quotes
        - `owned_fleet` — your drivers and teams
        - `hybrid` — both modes available
    CapabilitiesResponse:
      type: object
      required:
        - accountType
        - capabilities
      properties:
        accountType:
          $ref: '#/components/schemas/AccountType'
        capabilities:
          type: object
          required:
            - quotes
            - marketplaceDispatch
            - ownedFleetDispatch
            - returns
            - requirementEnforcement
            - driverLocationStreaming
            - routeOptimization
            - itemLevelPackage
          properties:
            quotes:
              type: boolean
              description: '`POST /quotes` supported'
            marketplaceDispatch:
              type: boolean
              description: Multi-carrier automatic dispatch
            ownedFleetDispatch:
              type: boolean
              description: |
                Account may direct dispatch to a named team via `dispatch.mode`. `false`
                for standard merchant accounts, where OnPoint assigns drivers by market
                and `GET /teams` returns an empty array.
            returns:
              type: boolean
              description: Return-to-store lifecycle (`return_in_progress`, `returned`)
            requirementEnforcement:
              type: boolean
              description: Age verification, barcode scan, etc. enforced at delivery
            driverLocationStreaming:
              type: boolean
              description: Periodic `delivery.driver_location_updated` webhooks
            routeOptimization:
              type: boolean
              description: Route planning and optimization features
            itemLevelPackage:
              type: boolean
              description: Line-item details in `package.items`
            cancellationFees:
              type: boolean
              description: Cancellation fees may apply and appear in pricing
            maxRequestsPerSecond:
              type: integer
              description: Rate limit for this API key
              examples:
                - 20
    HealthResponse:
      type: object
      required:
        - status
        - checkedAt
      properties:
        status:
          type: string
          enum:
            - healthy
            - degraded
            - unavailable
        checkedAt:
          type: string
          format: date-time
    QuoteRequest:
      type: object
      required:
        - clientOrderReference
        - pickup
        - dropoff
      properties:
        clientOrderReference:
          type: string
          maxLength: 128
          description: Your order ID for correlation. No delivery is persisted at quote time.
        pickup:
          $ref: '#/components/schemas/Location'
        dropoff:
          $ref: '#/components/schemas/Location'
        timeWindows:
          $ref: '#/components/schemas/TimeWindows'
        package:
          $ref: '#/components/schemas/Package'
        verification:
          $ref: '#/components/schemas/VerificationRequirements'
        handling:
          $ref: '#/components/schemas/HandlingOptions'
    QuoteResponse:
      type: object
      required:
        - clientOrderReference
        - quotes
      properties:
        clientOrderReference:
          type: string
        quotes:
          type: array
          description: Empty when no carrier can serve this route. Not an error.
          items:
            $ref: '#/components/schemas/QuoteOption'
        noQuoteReason:
          type: string
          description: Populated only when `quotes` is empty.
          enum:
            - outside_service_area
            - no_capacity
            - outside_operating_hours
            - unsupported_requirement
            - package_too_large
        expiresAt:
          type: string
          format: date-time
          description: Earliest expiry among returned quotes. Absent when `quotes` is empty.
    QuoteOption:
      type: object
      required:
        - quoteId
        - currency
        - totalPriceCents
      properties:
        quoteId:
          type: string
          description: Pass to `POST /deliveries` to dispatch at this price
        carrierId:
          type: string
          description: Carrier identifier
        carrierName:
          type: string
          description: Human-readable carrier name
        currency:
          type: string
          examples:
            - USD
        totalPriceCents:
          type: integer
          description: Total delivery fee in cents
        pickupEta:
          type: string
          format: date-time
        dropoffEta:
          type: string
          format: date-time
        pickupWindow:
          $ref: '#/components/schemas/TimeWindow'
        dropoffWindow:
          $ref: '#/components/schemas/TimeWindow'
    DeliveryCreateRequest:
      type: object
      required:
        - clientOrderReference
        - pickup
        - dropoff
      properties:
        clientOrderReference:
          type: string
          maxLength: 128
          description: |
            Your order ID, used for correlation and lookup. Must be unique across every
            delivery on your account for the lifetime of the account — not just among
            active ones. Reusing a reference returns `409` `DUPLICATE_ORDER`.

            This is **not** an idempotency key. Use the `Idempotency-Key` header for
            safe retries.
        quoteId:
          type: string
          description: Quote from `POST /quotes` to dispatch at a specific price
        pickup:
          $ref: '#/components/schemas/Location'
        dropoff:
          $ref: '#/components/schemas/Location'
        timeWindows:
          $ref: '#/components/schemas/TimeWindows'
        package:
          $ref: '#/components/schemas/Package'
        verification:
          $ref: '#/components/schemas/VerificationRequirements'
        handling:
          $ref: '#/components/schemas/HandlingOptions'
        tipAmountCents:
          type: integer
          minimum: 0
        metadata:
          type: object
          additionalProperties: true
          description: Opaque key-value data returned on reads and webhooks
        dispatch:
          $ref: '#/components/schemas/DispatchConfig'
    DispatchConfig:
      type: object
      description: |
        Routing control for dispatch-partner accounts.

        **Most integrations should omit this object entirely.** Standard merchant accounts
        get `marketplace`, and OnPoint decides who delivers — that is the product. This
        block exists for platform partners and the OnPoint dashboard, which run dispatch
        themselves against an OnPoint-recruited fleet.

        Sending `team_queue` or `auto_assign` on an account without
        `capabilities.ownedFleetDispatch` returns `422 UNSUPPORTED_REQUIREMENT`. It is
        never silently downgraded to `marketplace`, because a partner who thinks they
        pinned a delivery to a team and did not has no way to detect it.
      properties:
        mode:
          type: string
          enum:
            - marketplace
            - team_queue
            - auto_assign
          default: marketplace
          description: |
            - `marketplace` — OnPoint dispatches automatically (default, and the only
              value available to standard merchant accounts)
            - `team_queue` — place on a named team's queue (dispatch partners only)
            - `auto_assign` — auto-assign to the nearest available driver on the named
              team (dispatch partners only)
        teamReference:
          type: string
          description: |
            Team identifier from `GET /teams`. Required for `team_queue` and
            `auto_assign`; ignored for `marketplace`. An unknown value returns
            `422 INVALID_TEAM_REFERENCE`.
        dispatchStrategyId:
          type: string
          description: Optional dispatch strategy override (marketplace accounts)
    DeliveryUpdateRequest:
      type: object
      description: |
        Every field is optional. Omitted fields are left unchanged. Nested objects are
        shallow-merged; arrays are replaced wholesale.
      properties:
        pickup:
          $ref: '#/components/schemas/LocationPatch'
        dropoff:
          $ref: '#/components/schemas/LocationPatch'
        timeWindows:
          $ref: '#/components/schemas/TimeWindows'
        package:
          $ref: '#/components/schemas/Package'
        verification:
          $ref: '#/components/schemas/VerificationRequirements'
        handling:
          $ref: '#/components/schemas/HandlingOptions'
        tipAmountCents:
          type: integer
          minimum: 0
        metadata:
          type: object
          additionalProperties: true
    DeliveryResponse:
      type: object
      required:
        - deliveryId
        - clientOrderReference
        - status
      properties:
        deliveryId:
          type: string
          description: OnPoint delivery identifier
        clientOrderReference:
          type: string
        livemode:
          type: boolean
          description: |
            `false` for deliveries created with a sandbox key. Present on every delivery
            object and every webhook, so a test event that reaches production code is
            detectable rather than indistinguishable.
        status:
          $ref: '#/components/schemas/DeliveryStatus'
        statusReason:
          type: string
          description: Human-readable context for failed, cancelled, expired, or dispatch_failed
        statusReasonCode:
          $ref: '#/components/schemas/StatusReasonCode'
        pickup:
          $ref: '#/components/schemas/Location'
        dropoff:
          $ref: '#/components/schemas/Location'
        timeWindows:
          $ref: '#/components/schemas/TimeWindows'
        package:
          $ref: '#/components/schemas/Package'
        resolvedVerification:
          $ref: '#/components/schemas/ResolvedVerification'
        handling:
          $ref: '#/components/schemas/HandlingOptions'
        driver:
          $ref: '#/components/schemas/Driver'
        etas:
          $ref: '#/components/schemas/DeliveryEtas'
        proofOfDelivery:
          type: array
          items:
            $ref: '#/components/schemas/ProofOfDelivery'
        pricing:
          $ref: '#/components/schemas/Pricing'
        trackingUrl:
          type: string
          format: uri
          description: |
            Public live-tracking page for the recipient. It requires no credential — the
            URL is the only thing gating access and it embeds the delivery identifier, so
            treat the link itself as the secret and do not publish it anywhere you would
            not send the recipient directly.

            The page carries no personally identifying information: no recipient name, no
            full dropoff address, no driver surname, and no phone number. It shows delivery
            status, ETA, and a coarse driver position while en route. This is a contractual
            guarantee, not an implementation detail — the page will not start exposing PII
            without a version bump.
        metadata:
          type: object
          additionalProperties: true
        statusHistory:
          type: array
          items:
            $ref: '#/components/schemas/StatusHistoryEntry'
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    DeliveryListResponse:
      type: object
      required:
        - deliveries
      properties:
        deliveries:
          type: array
          items:
            $ref: '#/components/schemas/DeliveryResponse'
        nextCursor:
          type:
            - string
            - 'null'
          description: Cursor for the next page, or `null` on the final page.
    StatusHistoryEntry:
      type: object
      required:
        - status
        - occurredAt
      properties:
        status:
          $ref: '#/components/schemas/DeliveryStatus'
        occurredAt:
          type: string
          format: date-time
    DeliveryEtas:
      type: object
      properties:
        pickup:
          type: string
          format: date-time
        dropoff:
          type: string
          format: date-time
    CancelRequest:
      type: object
      properties:
        reason:
          type: string
          enum:
            - customer_cancelled
            - order_changed
            - duplicate_order
            - store_closed
            - other
        reasonNotes:
          type: string
          maxLength: 280
    DeliveryWebhook:
      type: object
      required:
        - eventId
        - eventType
        - occurredAt
        - delivery
      properties:
        eventId:
          type: string
          description: Unique event ID — use as deduplication key
        eventType:
          $ref: '#/components/schemas/DeliveryWebhookEventType'
        occurredAt:
          type: string
          format: date-time
        livemode:
          type: boolean
          description: |
            `false` for events from sandbox deliveries, including those driven by
            `POST /deliveries/{id}/simulate`. Assert on this in your handler so a
            simulated event can never be mistaken for a real one if a sandbox endpoint is
            ever misconfigured to point at production.
        previousStatus:
          $ref: '#/components/schemas/DeliveryStatus'
        delivery:
          $ref: '#/components/schemas/DeliveryResponse'
    DriverWebhook:
      type: object
      required:
        - eventId
        - eventType
        - occurredAt
        - driver
      properties:
        eventId:
          type: string
        eventType:
          $ref: '#/components/schemas/DriverWebhookEventType'
        occurredAt:
          type: string
          format: date-time
        livemode:
          type: boolean
        driver:
          $ref: '#/components/schemas/DriverProfile'
    DriverProfile:
      type: object
      required:
        - id
      properties:
        id:
          type: string
        name:
          type: string
        phoneNumber:
          type: string
        onDuty:
          type: boolean
        status:
          type: string
          enum:
            - active
            - invited
            - inactive
        teams:
          type: array
          items:
            type: string
        vehicleDescription:
          type: string
        installationAccess:
          $ref: '#/components/schemas/InstallationAccess'
        location:
          $ref: '#/components/schemas/GeoPoint'
        timeCreated:
          type: string
          format: date-time
        timeLastSeen:
          type: string
          format: date-time
    InstallationAccess:
      type: object
      readOnly: true
      description: |
        Which installations this driver is currently credentialed to enter.

        OnPoint uses this to filter the dispatch pool: an order for a stop with an
        `installation.code` is only offered to drivers holding a current credential
        scoped to that installation. That filtering is automatic — you do not request it.

        **What this deliberately does not contain.** Installation access vetting is
        performed against federal criminal-history systems under CJIS rules that require
        a dissemination log and prohibit onward disclosure. OnPoint therefore holds and
        exposes only a cleared/not-cleared assertion, the credential category, its expiry,
        and its installation scope. Underlying vetting detail is not available through
        this API, will not be added, and should not be requested.
      properties:
        status:
          type: string
          description: |
            `expiring_soon` is surfaced because credentials lapse and a driver whose pass
            expires mid-route is a gate denial waiting to happen.
          enum:
            - cleared
            - expiring_soon
            - expired
            - suspended
            - none
        credentialType:
          type:
            - string
            - 'null'
          enum:
            - dbids
            - cac
            - installation_pass
            - contractor_badge
            - null
        installations:
          type: array
          description: Installation codes this credential is valid for. Scope is per-installation, not global.
          items:
            type: string
          examples:
            - - INST-FTLIBERTY
              - INST-POPEAAF
        expiresAt:
          type:
            - string
            - 'null'
          format: date-time
    Team:
      type: object
      required:
        - id
        - name
      properties:
        id:
          type: string
        name:
          type: string
        workerCount:
          type: integer
        hubDestinationId:
          type:
            - string
            - 'null'
          description: Hub location for team dispatch
    Location:
      type: object
      description: Provide at least one of `address` or `storeReference`.
      anyOf:
        - required:
            - address
        - required:
            - storeReference
      properties:
        storeReference:
          type: string
          description: Pre-registered store or hub identifier from OnPoint onboarding
        address:
          oneOf:
            - type: string
              description: Single-line address (preferred — geocoded by OnPoint)
            - $ref: '#/components/schemas/StructuredAddress'
        latitude:
          type: number
          format: double
          description: Optional — improves accuracy when supplied with address
        longitude:
          type: number
          format: double
        contactName:
          type: string
          maxLength: 160
        contactPhone:
          type: string
          description: E.164 format
          examples:
            - '+14155551212'
        contactEmail:
          type: string
          format: email
        businessName:
          type: string
          maxLength: 80
        instructions:
          type: string
          maxLength: 280
          description: |
            Driver-facing free text for this stop.

            Do **not** encode building, room, gate, or unit information here. Use the
            structured `unit` and `installation` blocks — they are dispatchable, they
            drive carrier selection, and they are not silently truncated at 280
            characters the way a packed instruction string is.
        installation:
          $ref: '#/components/schemas/InstallationContext'
        unit:
          $ref: '#/components/schemas/UnitAddress'
    LocationPatch:
      type: object
      description: |
        Partial location update. Unlike `Location`, neither `address` nor `storeReference`
        is required — supply only the fields you want to change.

        Changing `address` or `storeReference` is only permitted before `pickup_complete`.
      properties:
        storeReference:
          type: string
        address:
          oneOf:
            - type: string
            - $ref: '#/components/schemas/StructuredAddress'
        latitude:
          type: number
          format: double
        longitude:
          type: number
          format: double
        contactName:
          type: string
          maxLength: 160
        contactPhone:
          type: string
          description: E.164 format
        contactEmail:
          type: string
          format: email
        businessName:
          type: string
          maxLength: 80
        instructions:
          type: string
          maxLength: 280
          description: Driver-facing instructions for this stop
        installation:
          $ref: '#/components/schemas/InstallationContext'
        unit:
          $ref: '#/components/schemas/UnitAddress'
    StructuredAddress:
      type: object
      required:
        - line1
        - city
        - postalCode
      properties:
        line1:
          type: string
        line2:
          type: string
        city:
          type: string
        state:
          type: string
        postalCode:
          type: string
        country:
          type: string
          default: US
        addressType:
          type: string
          readOnly: true
          default: domestic
          description: |
            Classification assigned by OnPoint during geocoding.

            `military_overseas` covers APO, FPO, and DPO addresses. These are **USPS-only
            destinations** — no commercial carrier can serve them, and UPS stopped
            accepting them entirely on 2 January 2025. OnPoint rejects them at quote time
            with `422` `UNSUPPORTED_DESTINATION` rather than accepting the order and
            failing at dispatch, which is what a single-line address string would cause.
          enum:
            - domestic
            - military_installation
            - military_overseas
            - po_box
            - international
    Installation:
      type: object
      required:
        - code
        - name
        - acceptingDeliveries
        - accessLevel
      properties:
        code:
          type: string
          examples:
            - INST-FTLIBERTY
        name:
          type: string
          examples:
            - Fort Liberty
        branch:
          type: string
          enum:
            - army
            - navy
            - air_force
            - marine_corps
            - space_force
            - coast_guard
            - joint
            - dla
        state:
          type: string
          pattern: ^[A-Z]{2}$
        acceptingDeliveries:
          type: boolean
          description: Whether commercial delivery is currently permitted at all.
        accessLevel:
          type: string
          enum:
            - open_post
            - id_required
            - escort_required
            - restricted
        fpconLevel:
          type:
            - string
            - 'null'
          enum:
            - normal
            - alpha
            - bravo
            - charlie
            - delta
            - null
        photographyPermitted:
          type: boolean
        imagingRestrictions:
          type:
            - string
            - 'null'
          enum:
            - none
            - no_recording_devices
            - vehicle_imaging_prohibited
            - null
        supportedDeliveryPoints:
          type: array
          items:
            type: string
            enum:
              - door
              - cq_desk
              - mailroom
              - front_desk
              - lobby
              - gate_handoff
              - locker
        permittedProofTypes:
          type: array
          description: |
            Verification mechanisms allowed here. Anything absent is suppressed and
            substituted — most commonly `photo`.
          items:
            type: string
            enum:
              - signature
              - photo
              - barcode
              - pin_code
              - identity
        prohibitedCargo:
          type: array
          description: Cargo attributes that cannot be brought onto this installation.
          items:
            $ref: '#/components/schemas/CargoAttribute'
        gates:
          type: array
          items:
            $ref: '#/components/schemas/InstallationGate'
        policyUpdatedAt:
          type: string
          format: date-time
          description: |
            When OnPoint last confirmed this policy. Posture can change faster than this
            record; treat it as the age of the answer, not a guarantee of freshness.
    InstallationGate:
      type: object
      properties:
        name:
          type: string
          examples:
            - All American Gate
        commercialVehiclesPermitted:
          type: boolean
        hours:
          type: string
          description: Human-readable operating hours for commercial traffic.
          examples:
            - Mon-Fri 05:00-22:00; Sat-Sun 06:00-20:00
        timezone:
          type: string
          description: IANA timezone the hours are expressed in.
          examples:
            - America/New_York
    InstallationWebhook:
      type: object
      required:
        - eventId
        - eventType
        - occurredAt
        - installation
      properties:
        eventId:
          type: string
        eventType:
          type: string
          enum:
            - installation.access_policy_changed
        occurredAt:
          type: string
          format: date-time
        livemode:
          type: boolean
        changed:
          type: array
          description: Which policy fields changed, so you can ignore updates you do not care about.
          items:
            type: string
            enum:
              - acceptingDeliveries
              - accessLevel
              - fpconLevel
              - photographyPermitted
              - imagingRestrictions
              - gates
              - permittedProofTypes
              - prohibitedCargo
        affectedDeliveryCount:
          type: integer
          description: Your in-flight or scheduled deliveries to this installation at the time of the change.
        installation:
          $ref: '#/components/schemas/Installation'
    InstallationListResponse:
      type: object
      required:
        - installations
      properties:
        installations:
          type: array
          items:
            $ref: '#/components/schemas/Installation'
        nextCursor:
          type:
            - string
            - 'null'
    ServiceabilityRequest:
      type: object
      required:
        - dropoff
      properties:
        pickup:
          $ref: '#/components/schemas/LocationPatch'
        dropoff:
          $ref: '#/components/schemas/LocationPatch'
        package:
          $ref: '#/components/schemas/Package'
        deliverAt:
          type: string
          format: date-time
          description: |
            When the delivery would occur. Defaults to now. Supply it for scheduled
            orders — gate hours and posture make serviceability time-dependent, so
            "deliverable now" does not imply "deliverable at 03:00."
    ServiceabilityResponse:
      type: object
      required:
        - serviceable
        - evaluatedAt
      properties:
        serviceable:
          type: boolean
        reasonCode:
          $ref: '#/components/schemas/StatusReasonCode'
        reason:
          type: string
          description: Prose explanation. Display it; do not parse it.
        installation:
          $ref: '#/components/schemas/InstallationContext'
        recommendedGate:
          type: string
        credentialedDriversAvailable:
          type: boolean
          description: |
            Whether at least one driver holding a current credential for this
            installation is available in the requested window.

            This is the check that prevents the failure customers report on other
            platforms — placing an order, waiting, and having it cancelled because the
            assigned courier turns out to have no base access. Deliberately a boolean:
            exposing a count would leak fleet size, and no merchant needs it.
        permittedProofTypes:
          type: array
          items:
            type: string
            enum:
              - signature
              - photo
              - barcode
              - pin_code
              - identity
        suppressedProofTypes:
          type: array
          description: Mechanisms that will be refused here even if you request them.
          items:
            type: string
            enum:
              - signature
              - photo
              - barcode
              - pin_code
              - identity
        supportedDeliveryPoints:
          type: array
          items:
            type: string
            enum:
              - door
              - cq_desk
              - mailroom
              - front_desk
              - lobby
              - gate_handoff
              - locker
        retryAfter:
          type: string
          format: date-time
          description: |
            Earliest time this stop is expected to become serviceable, when the blocker is
            temporal — a closed gate rather than a restricted post. Absent when the answer
            will not change on its own.
        evaluatedAt:
          type: string
          format: date-time
    EventListResponse:
      type: object
      required:
        - events
      properties:
        events:
          type: array
          items:
            $ref: '#/components/schemas/StoredEvent'
        nextCursor:
          type:
            - string
            - 'null'
    StoredEvent:
      type: object
      required:
        - eventId
        - eventType
        - occurredAt
        - data
      properties:
        eventId:
          type: string
          description: Matches the `eventId` of the webhook payload. Deduplicate on it.
        eventType:
          type: string
        occurredAt:
          type: string
          format: date-time
        livemode:
          type: boolean
        deliveryId:
          type: string
        deliveryAttempts:
          type: integer
          description: How many times OnPoint tried to POST this to your endpoint.
        lastDeliveryStatus:
          type: string
          description: Outcome of the most recent attempt. `pending` means attempts are ongoing.
          enum:
            - succeeded
            - failed
            - pending
            - no_endpoint_configured
        lastAttemptAt:
          type:
            - string
            - 'null'
          format: date-time
        data:
          type: object
          additionalProperties: true
          description: |
            The event payload, byte-identical to what the webhook carried or would have
            carried — so the same handler can process it without a separate code path.
    SimulateRequest:
      type: object
      required:
        - advanceTo
      properties:
        advanceTo:
          $ref: '#/components/schemas/DeliveryStatus'
        statusReasonCode:
          $ref: '#/components/schemas/StatusReasonCode'
        emitIntermediateEvents:
          type: boolean
          default: true
          description: |
            When `true`, every transition between the current status and `advanceTo`
            fires its own webhook, which is what production would do. Set `false` to jump
            straight to the target and emit only that one event.
        driverName:
          type: string
          description: Name for the simulated driver. Defaults to a generated one.
    SimulateResponse:
      type: object
      required:
        - deliveryId
        - status
      properties:
        deliveryId:
          type: string
        livemode:
          type: boolean
          description: Always `false`.
        status:
          $ref: '#/components/schemas/DeliveryStatus'
        emittedEvents:
          type: array
          description: Event types queued for delivery, in order.
          items:
            type: string
    InstallationContext:
      type: object
      description: |
        Military installation context for this stop.

        Supplying this is what routes the delivery to a driver who actually holds current
        credentials for that installation, instead of one who will be turned away at the
        gate. Omit it and the stop is treated as an ordinary commercial address.

        You normally only need `code`; everything else is resolved from
        `GET /installations/{code}` and returned on reads. Supply `gate` only when you
        have been told to use a specific one.
      properties:
        code:
          type: string
          description: OnPoint installation code. Enumerate via `GET /installations`.
          examples:
            - INST-FTLIBERTY
        name:
          type: string
          readOnly: true
          examples:
            - Fort Liberty
        gate:
          type: string
          description: Designated gate. Omit to let OnPoint select based on hours and cargo.
          examples:
            - All American Gate
        accessLevel:
          type: string
          readOnly: true
          description: |
            - `open_post` — no credential check for routine entry
            - `id_required` — driver must present a valid credential at the gate
            - `escort_required` — driver must be escorted by an authorized sponsor.
              Note that Trusted Traveler does **not** apply: a recipient cannot sponsor a
              commercial driver in, so escort is arranged by OnPoint or not at all.
            - `restricted` — closed to commercial delivery
          enum:
            - open_post
            - id_required
            - escort_required
            - restricted
        fpconLevel:
          type:
            - string
            - 'null'
          readOnly: true
          description: |
            Force Protection Condition at the time of the read, when published.

            This is **volatile** — posture can change with under 24 hours' notice and
            makes an address that was deliverable yesterday undeliverable today. Treat any
            cached serviceability answer as stale; subscribe to
            `installation.access_policy_changed` rather than polling.
          enum:
            - normal
            - alpha
            - bravo
            - charlie
            - delta
            - null
        photographyPermitted:
          type: boolean
          readOnly: true
          description: |
            When `false`, photo proof of delivery is suppressed regardless of what you
            requested, and the configured substitute is enforced instead. Photography in
            restricted areas can violate 18 U.S.C. § 795; this is not a merchant
            preference OnPoint can override.
        imagingRestrictions:
          type:
            - string
            - 'null'
          readOnly: true
          description: |
            Whether the installation restricts vehicles carrying imaging or recording
            hardware. Some installations deny entry to commercial vehicles suspected of
            having surveillance capability, which makes a dashcam a gate-denial risk and
            therefore a dispatch constraint, not just a POD question.
          enum:
            - none
            - no_recording_devices
            - vehicle_imaging_prohibited
            - null
    UnitAddress:
      type: object
      description: |
        Building- and room-level addressing for barracks, dormitories, family housing, and
        unit facilities.

        A street address is not sufficient inside an installation: buildings are numbered
        rather than named, rooms are not visible from the street, and the correct handoff
        point is frequently a staffed desk rather than a door. Drivers currently recover
        this from free-text instructions, which is why it is worth structuring.
      properties:
        buildingName:
          type: string
          examples:
            - Smith Hall
        buildingNumber:
          type: string
          examples:
            - C-3421
        floor:
          type: string
        room:
          type: string
          examples:
            - 214B
        unitDesignation:
          type: string
          description: Military unit designation, when it identifies the destination.
          examples:
            - 2-505 PIR
        deliveryPoint:
          type: string
          description: |
            Where the handoff actually happens. `cq_desk` is the Charge of Quarters desk —
            the staffed 24-hour desk in a barracks and the only lawful handoff point in
            many of them, since drivers cannot access individual rooms.
          enum:
            - door
            - cq_desk
            - mailroom
            - front_desk
            - lobby
            - gate_handoff
            - locker
    TimeWindows:
      type: object
      properties:
        pickup:
          $ref: '#/components/schemas/TimeWindow'
        dropoff:
          $ref: '#/components/schemas/TimeWindow'
        deliveryMode:
          type: string
          enum:
            - asap
            - scheduled
          default: asap
    TimeWindow:
      type: object
      required:
        - start
        - end
      properties:
        start:
          type: string
          format: date-time
        end:
          type: string
          format: date-time
    Package:
      type: object
      properties:
        description:
          type: string
          maxLength: 280
        itemsCount:
          type: integer
          minimum: 1
        valueCents:
          type: integer
          minimum: 0
          description: Total order value excluding delivery fee
        currency:
          type: string
          default: USD
        weightGrams:
          type: integer
          minimum: 0
        contains:
          type: array
          description: |
            What is in this package. OnPoint derives the legally required verification
            from these attributes and unions it with whatever you declared in
            `verification` — so declaring `alcohol` guarantees an age check even if you
            forget to ask for one, and you do not have to encode compliance rules in your
            own checkout.

            Attributes also gate carrier and destination selection: a carrier not licensed
            for alcohol is never offered a quote, and cargo an installation prohibits is
            rejected before dispatch rather than at the gate.
          items:
            $ref: '#/components/schemas/CargoAttribute'
        items:
          type: array
          items:
            $ref: '#/components/schemas/PackageItem'
    PackageItem:
      type: object
      required:
        - name
        - quantity
      properties:
        name:
          type: string
        quantity:
          type: integer
          minimum: 1
        sku:
          type: string
        barcode:
          type: string
        weightGrams:
          type: integer
          minimum: 0
        widthCm:
          type: number
        heightCm:
          type: number
        depthCm:
          type: number
        valueCents:
          type: integer
    VerificationLevel:
      type: string
      default: none
      description: |
        How strictly a verification step is enforced.

        - `required` — the driver cannot complete the stop without it. If it cannot be
          satisfied, the stop fails rather than completing unverified.
        - `preferred` — attempted, but the stop still completes if it cannot be
          satisfied. Check the resolved proof to find out which happened.
        - `none` — not attempted.

        `preferred` exists because a boolean cannot express it, and the alternative is
        merchants choosing between "fail the delivery over a missing signature" and
        "don't ask for one at all."
      enum:
        - required
        - preferred
        - none
    SignatureVerification:
      type: object
      properties:
        level:
          $ref: '#/components/schemas/VerificationLevel'
        collectSignerName:
          type: boolean
          default: false
        collectSignerRelationship:
          type: boolean
          default: false
          description: Captures the signer's relationship to the recipient, e.g. `spouse`, `neighbor`, `front desk`.
    PhotoVerification:
      type: object
      description: |
        Photographic proof at this stop.

        **May be refused.** Photography is prohibited or restricted in parts of many
        military installations under 18 U.S.C. § 795. Where the destination's policy
        forbids it, OnPoint suppresses photo capture regardless of the level you request
        and substitutes the configured alternative. The suppression is reported in
        `resolvedVerification.suppressed[]`, never silently.
      properties:
        level:
          $ref: '#/components/schemas/VerificationLevel'
        substituteWhenProhibited:
          type: string
          default: signature
          description: |
            What to enforce instead when photography is not permitted at this stop.
            Applied automatically; you do not need to detect the condition yourself.
          enum:
            - signature
            - pin_code
            - none
    BarcodeVerification:
      type: object
      description: Barcode or QR scanning, either scanned by the driver or displayed to a counterparty.
      properties:
        level:
          $ref: '#/components/schemas/VerificationLevel'
        mode:
          type: string
          default: scan
          description: |
            - `scan` — the driver scans a code present on the package or at the stop
            - `display` — the driver presents a code to be scanned by someone else,
              which is how commissary and locker handoffs are confirmed
          enum:
            - scan
            - display
        values:
          type: array
          description: Expected code values. A scan that matches none of these fails the stop.
          items:
            type: string
        symbologies:
          type: array
          description: Accepted symbologies. Omit to accept any that OnPoint supports.
          items:
            type: string
            enum:
              - qr
              - data_matrix
              - aztec
              - pdf417
              - code_128
              - code_39
              - code_93
              - codabar
              - ean_13
              - ean_8
              - itf
              - upc_a
              - upc_e
    PinCodeVerification:
      type: object
      description: A numeric code the recipient reads back to the driver.
      properties:
        level:
          $ref: '#/components/schemas/VerificationLevel'
        source:
          type: string
          default: onpoint_generated
          description: |
            - `onpoint_generated` — OnPoint generates the code and delivers it to the recipient
            - `recipient_phone_last4` — last four digits of the recipient's phone number
            - `merchant_provided` — you supply it in `value`
          enum:
            - onpoint_generated
            - recipient_phone_last4
            - merchant_provided
        value:
          type: string
          description: Required when `source` is `merchant_provided`. Never returned on reads.
          writeOnly: true
          pattern: ^[0-9]{4,8}$
    IdentityVerification:
      type: object
      description: |
        Government-ID check at the stop. OnPoint returns only a pass/fail assertion and
        the document category — never the document number, image, date of birth, or any
        other identity attribute.
      properties:
        level:
          $ref: '#/components/schemas/VerificationLevel'
        minimumAge:
          type: integer
          description: Minimum age in years the recipient must prove.
          minimum: 16
          maximum: 25
          examples:
            - 21
        acceptedDocuments:
          type: array
          description: Omit to accept any government-issued photo ID.
          items:
            type: string
            enum:
              - drivers_license
              - passport
              - state_id
              - military_cac
              - military_dependent_id
        matchRecipientName:
          type: boolean
          default: false
          description: Require the ID name to match `dropoff.contactName`.
    StopVerification:
      type: object
      description: |
        Verification steps for one stop. The same shape is used at pickup, dropoff, and
        on the return leg; not every mechanism is meaningful at every stop (identity
        checks at pickup, for example, are ignored for marketplace carriers).

        Anything you omit defaults to `none`. Requesting a mechanism your account is not
        enabled for returns `422` `UNSUPPORTED_REQUIREMENT` rather than silently
        downgrading — check `GET /capabilities` first.
      properties:
        signature:
          $ref: '#/components/schemas/SignatureVerification'
        photo:
          $ref: '#/components/schemas/PhotoVerification'
        barcode:
          $ref: '#/components/schemas/BarcodeVerification'
        pinCode:
          $ref: '#/components/schemas/PinCodeVerification'
        identity:
          $ref: '#/components/schemas/IdentityVerification'
    VerificationRequirements:
      type: object
      description: |
        What must be verified, and how strictly, at each stop.

        This replaces the flat requirement enum of earlier drafts. A flat enum could say
        *that* a signature was wanted but not whether it was mandatory, whose name to
        collect, which barcode value to expect, or what to do when the destination
        forbids photography.

        Requirements come from two places and are unioned: what you declare here, and
        what OnPoint derives from `package.contains`. Read
        `resolvedVerification` on the delivery to see what was actually enforced.
      properties:
        pickup:
          $ref: '#/components/schemas/StopVerification'
        dropoff:
          $ref: '#/components/schemas/StopVerification'
        return:
          $ref: '#/components/schemas/StopVerification'
    ResolvedVerification:
      type: object
      readOnly: true
      description: |
        What OnPoint will actually enforce, after unioning your declared requirements
        with those derived from cargo attributes and then applying destination policy.

        Always read this rather than assuming your request was applied verbatim. It is
        the only place that tells you an age check was added because you declared
        alcohol, or that photo proof was suppressed because the destination prohibits
        photography.
      properties:
        pickup:
          $ref: '#/components/schemas/StopVerification'
        dropoff:
          $ref: '#/components/schemas/StopVerification'
        return:
          $ref: '#/components/schemas/StopVerification'
        derived:
          type: array
          description: Requirements added by OnPoint that you did not request.
          items:
            type: object
            properties:
              stop:
                type: string
                enum:
                  - pickup
                  - dropoff
                  - return
              mechanism:
                type: string
                enum:
                  - signature
                  - photo
                  - barcode
                  - pin_code
                  - identity
              reason:
                type: string
                examples:
                  - package.contains includes alcohol; identity check raised to required with minimumAge 21
        suppressed:
          type: array
          description: |
            Requirements you asked for that OnPoint will **not** enforce, with the reason
            and the substitute applied. Surfaced explicitly so a suppressed photo POD is
            never mistaken for a carrier that simply forgot.
          items:
            type: object
            properties:
              stop:
                type: string
                enum:
                  - pickup
                  - dropoff
                  - return
              mechanism:
                type: string
                enum:
                  - signature
                  - photo
                  - barcode
                  - pin_code
                  - identity
              reasonCode:
                type: string
                enum:
                  - photography_prohibited_at_destination
                  - not_supported_by_assigned_carrier
                  - conflicts_with_unattended_handoff
              substitutedWith:
                type:
                  - string
                  - 'null'
                enum:
                  - signature
                  - photo
                  - barcode
                  - pin_code
                  - identity
                  - null
    CargoAttribute:
      type: string
      description: |
        A declaration about what is in the package. OnPoint derives the legally required
        verification from these, so you do not have to encode compliance rules yourself
        and cannot accidentally ship alcohol without an age check.

        Declaring an attribute your account is not approved to carry returns `422`
        `UNSUPPORTED_REQUIREMENT` at quote time rather than failing at dispatch.
      enum:
        - alcohol
        - tobacco
        - hemp
        - pharmacy
        - age_restricted_pharmacy
        - over_the_counter_medication
        - perishable_cold_chain
        - fragile
        - high_value
        - controlled_cargo
    HandlingOptions:
      type: object
      description: How the package is handed over, and what happens when it cannot be.
      properties:
        handoff:
          type: string
          default: hand_to_recipient
          description: |
            - `hand_to_recipient` — must be handed to a person
            - `leave_at_door` — unattended. Rejected with `422` when any `required`
              identity, signature, or PIN verification is also requested, because the
              two are contradictory.
            - `front_desk` — lobby, mailroom, or staffed counter
            - `cq_desk` — the Charge of Quarters desk, the standard handoff point for
              barracks where a driver cannot reach an individual room
            - `locker` — parcel locker or smart box
          enum:
            - hand_to_recipient
            - leave_at_door
            - front_desk
            - cq_desk
            - locker
        undeliverableAction:
          type: string
          default: return_to_pickup
          description: |
            What the driver does when the handoff cannot be completed. Defaulting to
            `return_to_pickup` is deliberate: silently abandoning a package is never the
            safe default, particularly for controlled cargo.
          enum:
            - return_to_pickup
            - fallback_handoff
            - dispose
        fallbackHandoff:
          type: string
          description: |
            Where to take the package when `undeliverableAction` is `fallback_handoff`.
            Required in that case.

            This exists because gate denial is a routine outcome, not an exception, and a
            driver turned away with no pre-agreed fallback has to improvise — which is
            how packages end up abandoned or returned at full cost.
          enum:
            - visitor_center
            - gate_guard_house
            - installation_mailroom
            - nearest_locker
            - merchant_designated_point
        twoPersonTeam:
          type: boolean
          default: false
          description: Dispatch two drivers. Used for heavy or high-value cargo.
    Driver:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        phoneNumber:
          type: string
          description: |
            Direct mobile number for the assigned driver, E.164 format. Not proxied and
            not masked — calls and messages reach the driver's personal handset.

            Use it for delivery coordination only. Do not retain it beyond the life of the
            delivery, add it to a contact or marketing list, or pass it to your end
            customer. OnPoint drivers are individually credentialed people, frequently
            onto military installations, and this is their personal contact detail.
        location:
          $ref: '#/components/schemas/GeoPoint'
        vehicleDescription:
          type: string
    GeoPoint:
      type: object
      properties:
        latitude:
          type: number
        longitude:
          type: number
        updatedAt:
          type: string
          format: date-time
    ProofOfDelivery:
      type: object
      properties:
        type:
          type: string
          enum:
            - photo
            - signature
            - barcode_scan
            - pincode_verification
        stage:
          type: string
          enum:
            - pickup
            - dropoff
            - return
        url:
          type: string
          format: uri
        text:
          type: string
          description: Scanned barcode value or entered pincode
        capturedAt:
          type: string
          format: date-time
    Pricing:
      type: object
      description: |
        Fully itemized so you can reconcile an invoice line-by-line without a support
        ticket. Every component is an integer in the minor unit of `currency`.

        **Invariant:** `totalPriceCents` equals the sum of all non-null component fields.
        A component that does not apply is `0`; a component your account never incurs is
        `null`. Do not derive the total yourself — surcharges may be added as
        backward-compatible new fields, and only `totalPriceCents` is authoritative.
      required:
        - currency
        - totalPriceCents
      properties:
        currency:
          type: string
          description: Uppercase ISO 4217 code.
          pattern: ^[A-Z]{3}$
          example: USD
        deliveryFeeCents:
          type: integer
          description: Base fee for the delivery itself.
        tipAmountCents:
          type: integer
          description: Tip destined for the driver, passed through in full.
        taxAmountCents:
          type:
            - integer
            - 'null'
          description: Tax on the delivery service. Null where not applicable.
        tollFeeCents:
          type:
            - integer
            - 'null'
          description: Tolls incurred on the route and passed through.
        waitFeeCents:
          type:
            - integer
            - 'null'
          description: |
            Charged when the driver waits beyond the free window at either stop. See
            `waitTimeMinutes` for the billed duration.
        waitTimeMinutes:
          type:
            - integer
            - 'null'
          description: Billable wait minutes that produced `waitFeeCents`.
        surchargeCents:
          type:
            - integer
            - 'null'
          description: |
            Demand, weather, or fuel surcharge. Itemized separately so a price change
            is explainable rather than an unexplained jump in `deliveryFeeCents`.
        cancellationFeeCents:
          type:
            - integer
            - 'null'
          description: Populated only once a cancellation incurs a fee.
        returnFeeCents:
          type:
            - integer
            - 'null'
          description: Populated only when a return leg is executed.
        totalPriceCents:
          type: integer
          description: Authoritative amount billed. Sum of the components above.
    DeliveryStatus:
      type: string
      description: |
        Normalized delivery status. Terminal statuses: `delivered`, `returned`, `cancelled`, `expired`.

        Deliveries reaching `delivered` are guaranteed to have passed through driver milestones in order.
      enum:
        - accepted
        - dispatch_failed
        - scheduled
        - reassigning
        - driver_assigned
        - driver_enroute_to_pickup
        - driver_at_pickup
        - pickup_complete
        - driver_enroute_to_dropoff
        - driver_at_dropoff
        - delivered
        - failed
        - return_in_progress
        - returned
        - cancelled
        - expired
    StatusReasonCode:
      type: string
      description: |
        Machine-readable cause accompanying a `failed`, `cancelled`, `dispatch_failed`,
        `expired`, or `return_in_progress` status. Branch on this; `statusReason` is
        prose and may change without notice.

        The access-control codes exist because "the driver could not get in" is a routine
        outcome on a military installation, not an exception, and it is operationally
        nothing like "the customer was not home." Collapsing the two — which every other
        delivery API does — makes the failure rate uninterpretable and gives support no
        way to route the problem.
      enum:
        - recipient_unavailable
        - recipient_refused
        - address_not_found
        - address_inaccessible
        - denied_at_gate
        - credential_expired
        - credential_not_valid_for_installation
        - gate_closed
        - fpcon_elevated
        - escort_unavailable
        - recipient_not_at_cq
        - photography_prohibited_no_substitute
        - verification_failed
        - age_verification_failed
        - package_damaged
        - package_not_ready
        - no_carrier_available
        - outside_service_area
        - unsupported_destination_type
        - cancelled_by_merchant
        - cancelled_by_recipient
        - cancelled_by_carrier
        - cancelled_by_onpoint
        - weather_or_road_closure
        - quote_expired
    DeliveryWebhookEventType:
      type: string
      enum:
        - delivery.accepted
        - delivery.dispatch_failed
        - delivery.scheduled
        - delivery.reassigning
        - delivery.driver_assigned
        - delivery.driver_enroute_to_pickup
        - delivery.driver_at_pickup
        - delivery.pickup_complete
        - delivery.driver_enroute_to_dropoff
        - delivery.driver_at_dropoff
        - delivery.delivered
        - delivery.failed
        - delivery.return_in_progress
        - delivery.returned
        - delivery.cancelled
        - delivery.expired
        - delivery.driver_location_updated
        - delivery.proof_of_delivery_added
        - delivery.eta_updated
    DriverWebhookEventType:
      type: string
      enum:
        - driver.created
        - driver.updated
        - driver.deleted
        - driver.duty_changed
    ErrorResponse:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          required:
            - code
            - message
          properties:
            code:
              type: string
              enum:
                - VALIDATION_ERROR
                - INVALID_ADDRESS
                - INVALID_PHONE_NUMBER
                - INVALID_TIME_CONSTRAINT
                - INVALID_STORE_REFERENCE
                - INVALID_TEAM_REFERENCE
                - UNSUPPORTED_REQUIREMENT
                - UNSUPPORTED_DESTINATION
                - INSTALLATION_NOT_SERVED
                - INSTALLATION_NOT_ACCEPTING_DELIVERIES
                - PROHIBITED_CARGO_FOR_DESTINATION
                - NO_ELIGIBLE_PROVIDERS
                - NO_CREDENTIALED_DRIVER_AVAILABLE
                - QUOTES_NOT_SUPPORTED
                - QUOTE_EXPIRED
                - DUPLICATE_ORDER
                - IDEMPOTENCY_KEY_REUSED
                - IDEMPOTENCY_CONFLICT
                - DELIVERY_NOT_FOUND
                - DRIVER_NOT_FOUND
                - INSTALLATION_NOT_FOUND
                - INVALID_STATE_TRANSITION
                - SANDBOX_ONLY
                - UNAUTHORIZED
                - RATE_LIMIT_EXCEEDED
                - SERVICE_UNAVAILABLE
                - INTERNAL_ERROR
            message:
              type: string
              description: Human-readable explanation. Do not parse — branch on `code`.
            details:
              type: object
              description: Additional context. `fieldErrors` is populated for validation failures.
              properties:
                fieldErrors:
                  type: array
                  items:
                    type: object
                    required:
                      - field
                      - code
                      - message
                    properties:
                      field:
                        type: string
                        description: JSON path of the offending field
                        examples:
                          - dropoff.contactPhone
                      code:
                        type: string
                        examples:
                          - INVALID_PHONE_NUMBER
                      message:
                        type: string
              additionalProperties: true
            requestId:
              type: string
              description: Include when contacting OnPoint support
