components:
  schemas:
    ShipBody:
      $defs:
        Dimensions:
          additionalProperties: false
          properties:
            h:
              description: height in inches
              exclusiveMinimum: 0
              title: H
              type: number
            l:
              description: length in inches
              exclusiveMinimum: 0
              title: L
              type: number
            w:
              description: width in inches
              exclusiveMinimum: 0
              title: W
              type: number
          required:
          - l
          - w
          - h
          title: Dimensions
          type: object
      additionalProperties: false
      description: 'POST /api/v1/dockd/orders/<so_number>/ship body.


        idempotency_key is validated as UUID4 (not raw str) so a non-UUID

        value surfaces as 422 invalid_body instead of slipping into the

        cache as opaque bytes.'
      properties:
        carrier:
          maxLength: 50
          minLength: 1
          title: Carrier
          type: string
        dims:
          anyOf:
          - $ref: '#/$defs/Dimensions'
          - type: 'null'
          default: null
        idempotency_key:
          format: uuid4
          title: Idempotency Key
          type: string
        manual_link:
          default: false
          title: Manual Link
          type: boolean
        operator_username:
          maxLength: 100
          minLength: 1
          title: Operator Username
          type: string
        ship_method:
          anyOf:
          - maxLength: 50
            minLength: 1
            type: string
          - type: 'null'
          default: null
          title: Ship Method
        shipping_cost:
          anyOf:
          - minimum: 0.0
            type: number
          - type: string
          - type: 'null'
          default: null
          title: Shipping Cost
        tracking:
          maxLength: 100
          minLength: 1
          title: Tracking
          type: string
        weight:
          anyOf:
          - exclusiveMinimum: 0
            type: number
          - type: 'null'
          default: null
          title: Weight
      required:
      - tracking
      - carrier
      - operator_username
      - idempotency_key
      title: ShipBody
      type: object
    VoidShipBody:
      additionalProperties: false
      description: 'POST /api/v1/dockd/orders/<so_number>/void-ship body.


        Body is intentionally minimal: the void route reverts a previously-

        successful ship. The operator typed a free-text reason in the dockd

        UI ("wrong box dimensions", "label printed but never applied",

        etc.); the cap matches item_fulfillments.void_reason VARCHAR(500).'
      properties:
        idempotency_key:
          format: uuid4
          title: Idempotency Key
          type: string
        operator_username:
          maxLength: 100
          minLength: 1
          title: Operator Username
          type: string
        reason:
          maxLength: 500
          minLength: 1
          title: Reason
          type: string
      required:
      - reason
      - operator_username
      - idempotency_key
      title: VoidShipBody
      type: object
  securitySchemes:
    WmsToken:
      description: Per-station bearer token. Must carry endpoints=['dockd.dispatch']
        and MUST NOT carry source_system / inbound_resources / event_types (mixed-direction
        tokens are rejected at the dispatcher with cross_direction_scope_violation).
      in: header
      name: X-WMS-Token
      type: apiKey
info:
  description: 'Per-station shipping API for the dockd integration. Three endpoints:
    GET /orders/<so_number> for load-on-scan, POST /ship to record a ship, POST /void-ship
    to reverse one. v1.9 ships the contract as DRAFT; X-Sentry-Canonical-Model: DRAFT-v1
    header on every response.'
  title: SentryWMS v1.9.0 Dockd
  version: 1.9.0
openapi: 3.1.0
paths:
  /api/v1/dockd/orders/{so_number}:
    get:
      description: The load-on-scan call. Returns the order shape dockd's UI needs
        to render either the 'ready to ship' or the 'already shipped, want to void?'
        branch. Warehouse scope is enforced at SELECT time; an order outside the token's
        warehouse_ids returns 404 not_found, identical to a genuinely missing order.
      operationId: get_dockd_order
      parameters:
      - in: path
        name: so_number
        required: true
        schema:
          maxLength: 128
          minLength: 1
          pattern: ^[A-Za-z0-9_\-#.]+$
          type: string
      responses:
        '200':
          content:
            application/json:
              schema:
                additionalProperties: false
                properties:
                  carrier:
                    type:
                    - string
                    - 'null'
                  customer_name:
                    type:
                    - string
                    - 'null'
                  customer_phone:
                    type:
                    - string
                    - 'null'
                  customer_shipping_paid:
                    type:
                    - number
                    - 'null'
                  external_id:
                    format: uuid
                    type: string
                  ff_created_at:
                    format: date-time
                    type:
                    - string
                    - 'null'
                  items:
                    items:
                      additionalProperties: false
                      properties:
                        display_name:
                          type: string
                        external_id:
                          format: uuid
                          type: string
                        qty:
                          type: integer
                        sku:
                          type: string
                        upc:
                          type:
                          - string
                          - 'null'
                      required:
                      - external_id
                      - sku
                      - display_name
                      - upc
                      - qty
                      type: object
                    type: array
                  marketplace:
                    type:
                    - string
                    - 'null'
                  memo:
                    type:
                    - string
                    - 'null'
                  order_date:
                    format: date-time
                    type:
                    - string
                    - 'null'
                  order_total:
                    type:
                    - number
                    - 'null'
                  ship_method:
                    type:
                    - string
                    - 'null'
                  shippable:
                    type: boolean
                  shippable_from_statuses:
                    items:
                      type: string
                    type: array
                  shipped_at:
                    format: date-time
                    type:
                    - string
                    - 'null'
                  shipped_by:
                    type:
                    - string
                    - 'null'
                  shipping_address:
                    additionalProperties: false
                    properties:
                      city:
                        type:
                        - string
                        - 'null'
                      country:
                        type:
                        - string
                        - 'null'
                      line1:
                        type:
                        - string
                        - 'null'
                      line2:
                        type:
                        - string
                        - 'null'
                      name:
                        type:
                        - string
                        - 'null'
                      phone:
                        type:
                        - string
                        - 'null'
                      postal_code:
                        type:
                        - string
                        - 'null'
                      state:
                        type:
                        - string
                        - 'null'
                    required:
                    - name
                    - line1
                    - line2
                    - city
                    - state
                    - postal_code
                    - country
                    - phone
                    type: object
                  so_number:
                    type: string
                  station_label:
                    type:
                    - string
                    - 'null'
                  status:
                    type: string
                  tracking_number:
                    type:
                    - string
                    - 'null'
                  warehouse_id:
                    type: integer
                required:
                - so_number
                - external_id
                - status
                - warehouse_id
                - customer_name
                - customer_phone
                - memo
                - shipping_address
                - ship_method
                - items
                - order_total
                - customer_shipping_paid
                - marketplace
                - order_date
                - ff_created_at
                - shippable
                - shippable_from_statuses
                - shipped_by
                - tracking_number
                - carrier
                - shipped_at
                - station_label
                type: object
          description: Order detail.
          headers:
            X-Sentry-Canonical-Model: &id001
              description: Always set to DRAFT-v1 in v1.9.0. Indicates the dockd contract
                may break at v2.0 alongside the inbound canonical model lock.
              schema:
                enum:
                - DRAFT-v1
                type: string
        '401':
          content:
            application/json:
              schema: &id002
                additionalProperties: true
                properties:
                  details:
                    type: object
                  error_kind:
                    type: string
                  message:
                    type: string
                required:
                - error_kind
                - message
                type: object
          description: Missing / invalid X-WMS-Token.
          headers:
            X-Sentry-Canonical-Model: *id001
        '403':
          content:
            application/json:
              schema: *id002
          description: Scope violation. error_kind one of cross_direction_scope_violation,
            endpoint_scope_violation.
          headers:
            X-Sentry-Canonical-Model: *id001
        '404':
          content:
            application/json:
              schema: *id002
          description: Order not found OR outside the token's warehouse scope. The
            two cases share a body to prevent enumeration via 404-vs-403 inference.
          headers:
            X-Sentry-Canonical-Model: *id001
        '422':
          content:
            application/json:
              schema: *id002
          description: Path-parameter validation failure (so_number regex / length).
          headers:
            X-Sentry-Canonical-Model: *id001
        '429':
          content:
            application/json:
              schema: *id002
          description: Per-token rate limit exceeded.
          headers:
            X-Sentry-Canonical-Model: *id001
      security:
      - WmsToken: []
      summary: Load an order for a dockd station
      tags:
      - dockd
  /api/v1/dockd/orders/{so_number}/ship:
    post:
      description: 'Updates sales_orders to SHIPPED, inserts an item_fulfillments
        row, writes audit, emits ship.confirmed/1 to the outbox. HTTP-layer idempotency
        on (token_id, idempotency_key); 72h cache window. Replays return 200 with
        header X-Idempotent-Replay: true and do not re-execute. source_txn_id on the
        outbox row equals the request''s idempotency_key, tying outbox dedup to HTTP
        dedup.'
      operationId: post_dockd_ship
      parameters:
      - in: path
        name: so_number
        required: true
        schema:
          maxLength: 128
          minLength: 1
          pattern: ^[A-Za-z0-9_\-#.]+$
          type: string
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ShipBody'
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                additionalProperties: false
                properties:
                  audit_log_id:
                    type: integer
                  fulfillment_id:
                    type: integer
                  shipped_at:
                    format: date-time
                    type: string
                  status:
                    enum:
                    - SHIPPED
                    type: string
                  tracking:
                    type: string
                required:
                - status
                - tracking
                - shipped_at
                - fulfillment_id
                - audit_log_id
                type: object
          description: 'Ship recorded OR cached replay (replay carries X-Idempotent-Replay:
            true).'
          headers:
            X-Idempotent-Replay: &id003
              description: Set to 'true' when the response is a cached replay of a
                prior successful POST with the same idempotency_key. The handler did
                not re-execute the ship / void logic.
              schema:
                enum:
                - 'true'
                type: string
            X-Sentry-Canonical-Model: *id001
        '401':
          content:
            application/json:
              schema: *id002
          description: Missing / invalid X-WMS-Token.
          headers:
            X-Sentry-Canonical-Model: *id001
        '403':
          content:
            application/json:
              schema: *id002
          description: Scope violation.
          headers:
            X-Sentry-Canonical-Model: *id001
        '404':
          content:
            application/json:
              schema: *id002
          description: Order not found OR outside warehouse scope.
          headers:
            X-Sentry-Canonical-Model: *id001
        '409':
          content:
            application/json:
              schema: *id002
          description: 'Conflict. error_kind one of: already_shipped (SO is already
            SHIPPED, details carry existing_tracking / carrier / shipped_at / shipped_by),
            idempotency_key_reused_with_different_body.'
          headers:
            X-Sentry-Canonical-Model: *id001
        '410':
          content:
            application/json:
              schema: *id002
          description: not_in_shippable_status. Details carry current_status and allowed_statuses.
          headers:
            X-Sentry-Canonical-Model: *id001
        '413':
          content:
            application/json:
              schema: *id002
          description: body_too_large; SENTRY_DOCKD_MAX_BODY_KB.
          headers:
            X-Sentry-Canonical-Model: *id001
        '422':
          content:
            application/json:
              schema: *id002
          description: invalid_body (Pydantic), invalid_so_number (path regex), or
            unknown_operator (operator_username does not resolve).
          headers:
            X-Sentry-Canonical-Model: *id001
        '429':
          content:
            application/json:
              schema: *id002
          description: Per-token rate limit exceeded.
          headers:
            X-Sentry-Canonical-Model: *id001
        '503':
          content:
            application/json:
              schema: *id002
          description: idempotency_lock_timeout. A concurrent ship with the same key
            blocked longer than the 5s lock_timeout. dockd should back off >=250ms
            and retry with the same key.
          headers:
            Retry-After:
              description: Suggested seconds before retry.
              schema:
                type: integer
            X-Sentry-Canonical-Model: *id001
      security:
      - WmsToken: []
      summary: Record a successful ship
      tags:
      - dockd
  /api/v1/dockd/orders/{so_number}/void-ship:
    post:
      description: Reverts sales_orders to its pre_ship_status (PICKED or PACKED)
        and clears tracking / carrier / shipped_at, marks the SHIPPED item_fulfillments
        row VOIDED with operator + reason + timestamp, rolls back sales_order_lines.quantity_shipped
        + status for re-ship symmetry, writes audit, emits ship.voided/1 with source_txn_id
        = idempotency_key. Same idempotency contract as POST /ship.
      operationId: post_dockd_void_ship
      parameters:
      - in: path
        name: so_number
        required: true
        schema:
          maxLength: 128
          minLength: 1
          pattern: ^[A-Za-z0-9_\-#.]+$
          type: string
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/VoidShipBody'
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                additionalProperties: false
                properties:
                  audit_log_id:
                    type: integer
                  status:
                    enum:
                    - PICKED
                    - PACKED
                    type: string
                  voided_at:
                    format: date-time
                    type: string
                required:
                - status
                - voided_at
                - audit_log_id
                type: object
          description: Void recorded OR cached replay.
          headers:
            X-Idempotent-Replay: *id003
            X-Sentry-Canonical-Model: *id001
        '401':
          content:
            application/json:
              schema: *id002
          description: Missing / invalid X-WMS-Token.
          headers:
            X-Sentry-Canonical-Model: *id001
        '403':
          content:
            application/json:
              schema: *id002
          description: Scope violation.
          headers:
            X-Sentry-Canonical-Model: *id001
        '404':
          content:
            application/json:
              schema: *id002
          description: Order not found OR outside warehouse scope.
          headers:
            X-Sentry-Canonical-Model: *id001
        '409':
          content:
            application/json:
              schema: *id002
          description: 'Conflict. error_kind one of: not_shipped (SO is not SHIPPED),
            idempotency_key_reused_with_different_body.'
          headers:
            X-Sentry-Canonical-Model: *id001
        '413':
          content:
            application/json:
              schema: *id002
          description: body_too_large.
          headers:
            X-Sentry-Canonical-Model: *id001
        '422':
          content:
            application/json:
              schema: *id002
          description: invalid_body, invalid_so_number, or unknown_operator.
          headers:
            X-Sentry-Canonical-Model: *id001
        '429':
          content:
            application/json:
              schema: *id002
          description: Per-token rate limit exceeded.
          headers:
            X-Sentry-Canonical-Model: *id001
        '503':
          content:
            application/json:
              schema: *id002
          description: idempotency_lock_timeout.
          headers:
            Retry-After:
              description: Suggested seconds before retry.
              schema:
                type: integer
            X-Sentry-Canonical-Model: *id001
      security:
      - WmsToken: []
      summary: Reverse a previously-successful ship
      tags:
      - dockd
tags:
- description: v1.9.0 dockd shipping surface
  name: dockd

