openapi: 3.0.1
info:
  title: Carriyo Fulfillment API
  version: '1.0'
  description: |-
    The Carriyo Fulfillment API covers the work between order allocation and carrier handoff: picking items off shelves, packing them into parcels, and managing customer-collection handovers. Bin-location mappings for the picker app are also managed here.
  contact:
    name: Carriyo Support
    url: https://help.carriyo.com
    email: support@carriyo.com
servers:
  - url: https://api.carriyo.com
    description: Production Server
  - url: https://demo-api.carriyo.com
    description: Demo Server
security:
  - OAuth2-Production: []
    ApiKey: []
    TenantId: []
  - OAuth2-Demo: []
    ApiKey: []
    TenantId: []
tags:
  - name: picks
    description: |-
      A **Pick** is the picker's work order. It represents the lifecycle of one picker pulling line items from shelves at a fulfillment location for one or more fulfillment orders. Items can be picked off the shelf, restocked back, or recorded as mispicks when something goes wrong (wrong item, damaged unit).

      State changes go through dedicated action endpoints (`/start`, `/complete`, `/items/pick`, `/items/restock`, `/items/mispick`) rather than a generic PUT, so the action verb is part of the audit trail.
  - name: packs
    description: |-
      A **Pack** is the packer's work order. It represents one packer at a packing station consolidating items from a completed pick into one or more parcels (`packages`). Completing a pack can produce a shipment (for delivery fulfillment orders) or trigger Collection creation (for click-and-collect fulfillment orders).

      State changes go through dedicated action endpoints (`/start`, `/complete`, `/items/pack`, `/items/unpack`, `/packages`, `/create-shipment`).
  - name: collections
    description: |-
      A **Collection** is the customer-pickup runtime entity. When a Pack completes for a click-and-collect fulfillment order, the platform auto-creates a Collection. The customer later arrives at the location, identity is verified via OTP, and items are handed over.

      There is no `POST /orders/collections`; Collections are a side effect of `POST /orders/packs/{packId}/complete`. The endpoints below cover the lifecycle from creation onwards.
  - name: bin-locations
    description: |-
      A **Bin location** is an SKU-to-bin mapping within a fulfillment location: which physical bins on the warehouse floor hold a given SKU. The picker app reads these mappings to direct pickers efficiently.

      Records are keyed by `(tenantId, locationId, sku)`. Bulk import is supported for tenants migrating from another system.
paths:
  /orders/picks:
    post:
      tags:
        - picks
      summary: Create a pick
      operationId: create-pick
      description: |-
        Creates a pick work order for one or more fulfillment-order line items at a single location.

        ## When to use this

        Call this when you want to create a pick manually. In many setups the Fulfillment App auto-creates picks on allocation (when `auto_create_picks` is enabled in the location's Fulfillment App settings), so a manual create is only needed when auto-creation is off or when you want fine-grained control over which items go into which pick.

        ## Behavior

        Carriyo determines the **pick type** from the items you supply:

        | Scenario | Pick type | Requirement |
        |---|---|---|
        | All items belong to one fulfillment order | `ORDER_PICK` | None. |
        | Items span multiple fulfillment orders and every FO is fully included | `CLUSTER_PICK` | `cluster_picking_enabled` must be `true` in the location's Fulfillment App settings. |
        | Items span multiple fulfillment orders but only a subset of each FO's items is included | `ZONE_PICK` | `split_picking_enabled` must be `true`. |

        On success, Carriyo:

        1. Moves each referenced fulfillment-order line item from `allocated` to `pick_in_progress`. If the requested quantity is less than the line item's total, the line item is split: the original keeps the remaining quantity as `allocated`, and a new line item is created with the requested quantity as `pick_in_progress`.
        2. Assigns a picker (if not provided in the request, assignment uses the configured strategy: `work_load` or `manual`).
        3. Generates a **pick list** document (PDF) attached to the pick.

        ## Validation

        - `location_id` is required. All items must belong to fulfillment orders at this location.
        - At least one item is required.
        - Each line item must be in `allocated` status.
        - Each line item must not already belong to another active (non-terminal) pick.
        - `quantity` must not exceed the line item's available allocated quantity.
        - If `picker` is provided, the user must have `pick` permission for the location.
      requestBody:
        required: true
        description: The location, optional picker, and the line items to pick.
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/pick-request'
            examples:
              single-order:
                summary: Pick for a single fulfillment order
                value:
                  location_id: LOC_001
                  picker: user@example.com
                  items:
                    - order_id: ORD_1001
                      fulfillment_order_id: FO_2001
                      line_item_id: LI_3001
                      quantity: 2
                    - order_id: ORD_1001
                      fulfillment_order_id: FO_2001
                      line_item_id: LI_3002
                      quantity: 1
              auto-assign:
                summary: Pick with auto-assigned picker
                value:
                  location_id: LOC_001
                  items:
                    - order_id: ORD_1001
                      fulfillment_order_id: FO_2001
                      line_item_id: LI_3001
                      quantity: 3
      responses:
        '201':
          description: Pick created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/pick-response'
        '400':
          description: Validation failed. The response body contains a plain-text error message.
  /orders/picks/{pickId}:
    get:
      tags:
        - picks
      summary: Get a pick
      operationId: get-pick
      description: |-
        Returns the full pick record, including all items, documents, and timestamps.

        Use this endpoint when you need the complete pick data. The list endpoints (`/order/{orderId}`, `/fulfillment-order/{foId}`) return lookup records with only summary fields (`pick_id`, `location_id`, `status`, `creation_date`); they do not include `items`, `documents`, or detailed timestamps.
      parameters:
        - name: pickId
          in: path
          required: true
          schema:
            type: string
          description: The pick identifier (e.g. `PIK_123`).
      responses:
        '200':
          description: The full pick record.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/pick-response'
        '400':
          description: Pick not found.
  /orders/picks/order/{orderId}:
    get:
      tags:
        - picks
      summary: List picks for an order
      operationId: list-picks-for-order
      description: |-
        Returns all picks associated with an order.

        These are **lookup records** containing only summary fields: `pick_id`, `tenant`, `location_id`, `status`, and `creation_date`. To get the full pick (items, documents, timestamps), call Get pick with the `pick_id`.
      parameters:
        - name: orderId
          in: path
          required: true
          schema:
            type: string
          description: The order identifier.
      responses:
        '200':
          description: Array of pick summary records. May be empty if no picks exist for this order.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/pick-response'
  /orders/picks/fulfillment-order/{fulfillmentOrderId}:
    get:
      tags:
        - picks
      summary: List picks for a fulfillment order
      operationId: list-picks-for-fulfillment-order
      description: |-
        Returns all picks associated with a fulfillment order.

        These are **lookup records** containing only summary fields: `pick_id`, `tenant`, `location_id`, `status`, and `creation_date`. To get the full pick (items, documents, timestamps), call Get pick with the `pick_id`.
      parameters:
        - name: fulfillmentOrderId
          in: path
          required: true
          schema:
            type: string
          description: The fulfillment order identifier.
      responses:
        '200':
          description: Array of pick summary records. May be empty if no picks exist for this fulfillment order.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/pick-response'
  /orders/picks/{pickId}/start:
    post:
      tags:
        - picks
      summary: Start a pick
      operationId: start-pick
      description: |-
        Transitions the pick from `open` to `processing`. The picker is now walking the floor.

        ## Validation

        - The pick must be in `open` status.
        - A `picker` must already be assigned (set at creation or via Reassign pick).

        No request body is required.
      parameters:
        - name: pickId
          in: path
          required: true
          schema:
            type: string
          description: The pick identifier.
      responses:
        '200':
          description: Pick started. Status is now `processing`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/pick-response'
        '400':
          description: Pick is not in `open` status, or no picker is assigned.
  /orders/picks/{pickId}/cancel:
    post:
      tags:
        - picks
      summary: Cancel a pick
      operationId: cancel-pick
      description: |-
        Cancels the pick. The outcome depends on the current status and whether any items have been picked.

        ## Behavior

        | Current status | Items picked? | Result | Line-item effect |
        |---|---|---|---|
        | `open` | n/a | `cancelled` | All line items revert to `allocated`. |
        | `processing` | No | `cancelled` | All line items revert to `allocated`. |
        | `processing` | Yes | `stopped` | No revert. Items stay `pick_in_progress`. |
        | `stopped` | n/a | `cancelled` | All line items revert to `allocated`. |

        A `stopped` pick means items were partially picked and the pick was cancelled mid-flow. You can cancel a `stopped` pick again to fully cancel it and revert line items.

        Picks in `completed` or `cancelled` status cannot be cancelled.

        The request body is optional. If provided, `reason_code` is stored on the pick for auditing.
      parameters:
        - name: pickId
          in: path
          required: true
          schema:
            type: string
          description: The pick identifier.
      requestBody:
        required: false
        description: Optional cancellation reason.
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/cancel-pick-request'
      responses:
        '200':
          description: Pick cancelled (or stopped if items were partially picked).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/pick-response'
        '400':
          description: Pick is already in a terminal status (`completed` or `cancelled`).
  /orders/picks/{pickId}/reassign:
    post:
      tags:
        - picks
      summary: Reassign a pick
      operationId: reassign-pick
      description: |-
        Reassigns the pick to a different picker.

        ## Validation

        - The pick must be in `open` status.
        - The new picker must have `pick` permission for the pick's location.
      parameters:
        - name: pickId
          in: path
          required: true
          schema:
            type: string
          description: The pick identifier.
      requestBody:
        required: true
        description: The new picker to assign.
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/reassign-pick-request'
      responses:
        '200':
          description: Pick reassigned.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/pick-response'
        '400':
          description: Pick is not in `open` status, or the picker does not have `pick` permission.
  /orders/picks/{pickId}/complete:
    post:
      tags:
        - picks
      summary: Complete a pick
      operationId: complete-pick
      description: |-
        Completes the pick and hands it over to packing. This is the handover step.

        ## Validation

        - The pick must be in `processing` status.
        - Every item must be fully accounted for: `quantity_picked + sum(mispick quantities) == quantity` for each item.

        ## Behavior

        On completion, Carriyo updates each fulfillment-order line item based on what was picked:

        | Outcome | Line-item effect |
        |---|---|
        | Item fully picked (no mispicks) | `pick_in_progress` → `picked` |
        | Item partially picked with mispicks | `picked` (quantity adjusted to the picked amount) |
        | Item fully mispicked (zero picked) | Line item `cancelled` |

        If **every** item in the pick has `quantity_picked = 0` (all mispicked), the pick status becomes `cancelled` instead of `completed`, and all line items revert to `allocated`.

        No request body is required.
      parameters:
        - name: pickId
          in: path
          required: true
          schema:
            type: string
          description: The pick identifier.
      responses:
        '200':
          description: Pick completed (or cancelled if all items were mispicked).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/pick-response'
        '400':
          description: Pick is not in `processing` status, or not all items are accounted for.
  /orders/picks/{pickId}/items/pick:
    post:
      tags:
        - picks
      summary: Record items picked off shelf
      operationId: pick-items
      description: |-
        Records that the picker has collected items from the shelf. Call this incrementally as the picker works through the list.

        ## Behavior

        For each item in the request, Carriyo increments `quantity_picked` on the matching pick item. The fulfillment-order line items stay in `pick_in_progress` until you call Complete pick.

        You can call this endpoint multiple times. Each call adds to the running total.

        ## Validation

        - The pick must be in `processing` status.
        - Each item must exist in the pick (matched by `fulfillment_order_id` + `line_item_id`).
        - `quantity` must not exceed `item.quantity - item.quantity_picked - sum(mispick quantities)`.
      parameters:
        - name: pickId
          in: path
          required: true
          schema:
            type: string
          description: The pick identifier.
      requestBody:
        required: true
        description: Array of items to mark as picked.
        content:
          application/json:
            schema:
              type: array
              items:
                $ref: '#/components/schemas/pick-item-action-request'
            examples:
              pick-two-items:
                summary: Pick two line items
                value:
                  - fulfillment_order_id: FO_2001
                    line_item_id: LI_3001
                    quantity: 2
                  - fulfillment_order_id: FO_2001
                    line_item_id: LI_3002
                    quantity: 1
      responses:
        '200':
          description: Updated pick with incremented `quantity_picked` values.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/pick-response'
        '400':
          description: Pick is not in `processing` status, or quantity exceeds what remains to be picked.
  /orders/picks/{pickId}/items/restock:
    post:
      tags:
        - picks
      summary: Restock items back to shelf
      operationId: restock-pick-items
      description: |-
        Reverses a previous pick action, putting items back on the shelf. Use this when items were picked in error.

        ## Behavior

        Decrements `quantity_picked` on each matching pick item. If the fulfillment-order line item was already moved to `picked` (e.g. in a prior partial completion), it reverts to `pick_in_progress`.

        ## Validation

        - The pick must be in `processing` or `stopped` status.
        - `quantity` must not exceed the item's current `quantity_picked`.
      parameters:
        - name: pickId
          in: path
          required: true
          schema:
            type: string
          description: The pick identifier.
      requestBody:
        required: true
        description: Array of items to restock.
        content:
          application/json:
            schema:
              type: array
              items:
                $ref: '#/components/schemas/pick-item-action-request'
      responses:
        '200':
          description: Updated pick with decremented `quantity_picked` values.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/pick-response'
        '400':
          description: Pick is not in `processing` or `stopped` status, or quantity exceeds what was picked.
  /orders/picks/{pickId}/items/mispick:
    post:
      tags:
        - picks
      summary: Record mispicked items
      operationId: mispick-items
      description: |-
        Records items that could not be picked: out of stock, damaged, wrong item, or any other warehouse-floor issue.

        ## Behavior

        Appends a mispick record (quantity + reason) to the matching pick item's `mispicks` array. The fulfillment-order line item status does not change until Complete pick.

        At completion, items that are fully mispicked (zero picked, all mispicked) are cancelled. Items with a mix of picked and mispicked quantities have their quantity adjusted to the picked amount.

        ## Validation

        - The pick must be in `processing` status.
        - `quantity` must not exceed `item.quantity - item.quantity_picked - sum(existing mispick quantities)`.
        - `reason` is a free-text string describing why the item couldn't be picked.
      parameters:
        - name: pickId
          in: path
          required: true
          schema:
            type: string
          description: The pick identifier.
      requestBody:
        required: true
        description: Array of mispick records.
        content:
          application/json:
            schema:
              type: array
              items:
                $ref: '#/components/schemas/mispick-item-request'
            examples:
              damaged-item:
                summary: One item damaged on shelf
                value:
                  - fulfillment_order_id: FO_2001
                    line_item_id: LI_3001
                    quantity: 1
                    reason: Item damaged on shelf
      responses:
        '200':
          description: Updated pick with mispick records appended.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/pick-response'
        '400':
          description: Pick is not in `processing` status, or quantity exceeds what remains.
  /orders/packs:
    post:
      tags:
        - packs
      summary: Create a pack
      operationId: create-pack
      description: |-
        Creates a pack work order for one or more fulfillment-order line items at a single location.

        ## When to use this

        Call this after items have been picked (or directly after allocation if your workflow skips the pick stage). Each item in the request must be in `allocated` or `picked` status.

        ## Behavior

        On success, Carriyo:

        1. Moves each referenced line item from `allocated` or `picked` to `pack_in_progress`. If the requested quantity is less than the line item's total, the line item is split.
        2. Generates a **packing list** document (PDF) attached to the pack.
        3. Creates one default empty package per unique fulfillment order in the pack.

        ## Validation

        - `location_id` is required. All items must belong to fulfillment orders at this location.
        - At least one item is required.
        - Each line item must be in `allocated` or `picked` status.
        - Each line item must not already belong to another active (non-terminal) pack.
        - `quantity` must not exceed the line item's available quantity.
        - If `pick_id` is provided on an item, the referenced pick must be in `completed` status.
      requestBody:
        required: true
        description: The location, optional packer/station, and the line items to pack.
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/pack-request'
            examples:
              after-pick:
                summary: Pack items from a completed pick
                value:
                  location_id: LOC_001
                  packing_station: STATION_A
                  packer: packer@example.com
                  items:
                    - order_id: ORD_1001
                      fulfillment_order_id: FO_2001
                      line_item_id: LI_3001
                      pick_id: PIK_142
                      quantity: 2
              without-pick:
                summary: Pack items directly (no pick stage)
                value:
                  location_id: LOC_001
                  items:
                    - order_id: ORD_1001
                      fulfillment_order_id: FO_2001
                      line_item_id: LI_3001
                      quantity: 3
      responses:
        '201':
          description: Pack created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/pack-response'
        '400':
          description: Validation failed.
  /orders/packs/{packId}:
    get:
      tags:
        - packs
      summary: Get a pack
      operationId: get-pack
      description: |-
        Returns the full pack record, including all items, packages, documents, and timestamps.

        The list endpoints (`/order/{orderId}`, `/fulfillment-order/{foId}`, `/pick/{pickId}`) return lookup records with only summary fields. Use this endpoint for the complete data.
      parameters:
        - name: packId
          in: path
          required: true
          schema:
            type: string
          description: The pack identifier (e.g. `PAK_123`).
      responses:
        '200':
          description: The full pack record.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/pack-response'
        '400':
          description: Pack not found.
  /orders/packs/order/{orderId}:
    get:
      tags:
        - packs
      summary: List packs for an order
      operationId: list-packs-for-order
      description: |-
        Returns all packs associated with an order. These are **lookup records** with only summary fields. Use Get pack for the full record.
      parameters:
        - name: orderId
          in: path
          required: true
          schema:
            type: string
          description: The order identifier.
      responses:
        '200':
          description: Array of pack summary records.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/pack-response'
  /orders/packs/fulfillment-order/{fulfillmentOrderId}:
    get:
      tags:
        - packs
      summary: List packs for a fulfillment order
      operationId: list-packs-for-fulfillment-order
      description: |-
        Returns all packs associated with a fulfillment order. These are **lookup records** with only summary fields. Use Get pack for the full record.
      parameters:
        - name: fulfillmentOrderId
          in: path
          required: true
          schema:
            type: string
          description: The fulfillment order identifier.
      responses:
        '200':
          description: Array of pack summary records.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/pack-response'
  /orders/packs/pick/{pickId}:
    get:
      tags:
        - packs
      summary: List packs for a pick
      operationId: list-packs-for-pick
      description: |-
        Returns all packs that reference a given pick. Use this to find which packs were created from a completed pick.
      parameters:
        - name: pickId
          in: path
          required: true
          schema:
            type: string
          description: The pick identifier.
      responses:
        '200':
          description: Array of pack summary records.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/pack-response'
  /orders/packs/{packId}/start:
    post:
      tags:
        - packs
      summary: Start a pack
      operationId: start-pack
      description: |-
        Transitions the pack from `open` to `processing`. The packer is now at the station building parcels.

        ## Validation

        - The pack must be in `open` status.
        - Both `packing_station` and `packer` must be set (either at creation or via Reassign pack).

        No request body is required.
      parameters:
        - name: packId
          in: path
          required: true
          schema:
            type: string
          description: The pack identifier.
      responses:
        '200':
          description: Pack started. Status is now `processing`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/pack-response'
        '400':
          description: Pack is not in `open` status, or packing station / packer not assigned.
  /orders/packs/{packId}/reassign:
    post:
      tags:
        - packs
      summary: Reassign a pack
      operationId: reassign-pack
      description: |-
        Reassigns the pack to a different packer and/or packing station. At least one of `packing_station` or `packer` must be provided.

        ## Validation

        - The pack must be in `open` status.
        - If `packing_station` is provided, it must be a valid station for the location.
        - If `packer` is provided, the user must have `pack` permission for the location.
      parameters:
        - name: packId
          in: path
          required: true
          schema:
            type: string
          description: The pack identifier.
      requestBody:
        required: true
        description: The new packer and/or packing station.
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/reassign-pack-request'
      responses:
        '200':
          description: Pack reassigned.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/pack-response'
        '400':
          description: Pack is not in `open` status, or validation failed.
  /orders/packs/{packId}/packages:
    post:
      tags:
        - packs
      summary: Add a package to the pack
      operationId: add-package-to-pack
      description: |-
        Adds a new physical package (parcel) to the pack. Each package is assigned a unique `package_id` (prefixed `PKG_`).

        A pack can have many packages. Each package belongs to one order and one fulfillment order. Items are placed into packages via Pack items.

        ## Behavior

        The package is created empty with the provided dimensions and weight constraints. You then use Pack items to assign line items into it.

        ## Validation

        - The pack must be in `open` or `processing` status.
        - `order_id` and `fulfillment_order_id` are required and must match items in the pack.
      parameters:
        - name: packId
          in: path
          required: true
          schema:
            type: string
          description: The pack identifier.
      requestBody:
        required: true
        description: The package to add with its dimensions and weight constraints.
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/add-package-request'
      responses:
        '200':
          description: Package added. The full pack is returned with the new package in the `packages` array.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/pack-response'
        '400':
          description: Pack not in an active status, or order/FO mismatch.
  /orders/packs/{packId}/packages/{packageId}:
    put:
      tags:
        - packs
      summary: Update a package
      operationId: update-package
      description: |-
        Updates the physical properties of a package (carton type, dimensions, weight). All fields are optional; only provided fields are updated.

        ## Validation

        - The pack must be in `open` or `processing` status.
        - The package must not have an active (non-cancelled) shipment. Once a shipment is booked for a package, update the shipment directly.
      parameters:
        - name: packId
          in: path
          required: true
          schema:
            type: string
          description: The pack identifier.
        - name: packageId
          in: path
          required: true
          schema:
            type: string
          description: The package identifier (e.g. `PKG_456`).
      requestBody:
        required: true
        description: The fields to update on the package.
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/update-package-request'
      responses:
        '200':
          description: Package updated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/pack-response'
        '400':
          description: Pack not in an active status, or package has an active shipment.
    delete:
      tags:
        - packs
      summary: Remove a package
      operationId: delete-package
      description: |-
        Removes a package from the pack. Any items in the package are unpacked first (their `quantity_packed` on the pack item is decremented).

        ## Validation

        - The pack must be in `open` or `processing` status.
        - The package must not have an active (non-cancelled) shipment.
      parameters:
        - name: packId
          in: path
          required: true
          schema:
            type: string
          description: The pack identifier.
        - name: packageId
          in: path
          required: true
          schema:
            type: string
          description: The package identifier.
      responses:
        '200':
          description: Package removed. The full pack is returned without the deleted package.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/pack-response'
        '400':
          description: Pack not in an active status, or package has an active shipment.
  /orders/packs/{packId}/reset-packages:
    post:
      tags:
        - packs
      summary: Reset packages on the pack
      operationId: reset-pack-packages
      description: |-
        Clears all packing progress and replaces all packages with one default empty package per unique fulfillment order in the pack.

        Use this when the packer needs to start over. All items are unpacked (`quantity_packed` reset to 0) and existing packages are deleted.

        ## Validation

        - The pack must be in `open` or `processing` status.
        - No package in the pack can have an active (non-cancelled) shipment.

        No request body is required.
      parameters:
        - name: packId
          in: path
          required: true
          schema:
            type: string
          description: The pack identifier.
      responses:
        '200':
          description: Pack reset with fresh default packages.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/pack-response'
        '400':
          description: Pack not in an active status, or a package has an active shipment.
  /orders/packs/{packId}/items/pack:
    post:
      tags:
        - packs
      summary: Place items into packages
      operationId: pack-items
      description: |-
        Places line items into a specific package. Call this as the packer scans or places each item into a parcel.

        ## Behavior

        For each item in the request, Carriyo increments `quantity_packed` on the matching pack item and adds a `PackageItem` entry to the target package's `items` array.

        You can call this endpoint multiple times. Each call adds to the running total.

        ## Validation

        - The pack must be in `processing` status.
        - Each `package_id` must exist in the pack and belong to the same fulfillment order as the line item.
        - `quantity` must not exceed `item.quantity - item.quantity_packed`.
      parameters:
        - name: packId
          in: path
          required: true
          schema:
            type: string
          description: The pack identifier.
      requestBody:
        required: true
        description: Array of items to place into packages.
        content:
          application/json:
            schema:
              type: array
              items:
                $ref: '#/components/schemas/pack-item-action-request'
            examples:
              pack-into-parcel:
                summary: Place two items into a package
                value:
                  - line_item_id: LI_3001
                    fulfillment_order_id: FO_2001
                    package_id: PKG_456
                    quantity: 2
                    selection_method: SCANNER
                  - line_item_id: LI_3002
                    fulfillment_order_id: FO_2001
                    package_id: PKG_456
                    quantity: 1
                    selection_method: MANUAL
      responses:
        '200':
          description: Updated pack with incremented `quantity_packed` values and items added to the package.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/pack-response'
        '400':
          description: Pack is not in `processing` status, or quantity/package validation failed.
  /orders/packs/{packId}/items/unpack:
    post:
      tags:
        - packs
      summary: Remove items from packages
      operationId: unpack-items
      description: |-
        Removes items from a package, reversing a previous pack action. Use this when an item was placed in the wrong parcel.

        ## Behavior

        Decrements `quantity_packed` on the matching pack item and removes the corresponding quantity from the package's `items` array.

        ## Validation

        - The pack must be in `processing` status.
        - `quantity` must not exceed the item's current packed quantity in the specified package.
      parameters:
        - name: packId
          in: path
          required: true
          schema:
            type: string
          description: The pack identifier.
      requestBody:
        required: true
        description: Array of items to remove from packages.
        content:
          application/json:
            schema:
              type: array
              items:
                $ref: '#/components/schemas/unpack-item-action-request'
      responses:
        '200':
          description: Updated pack with decremented `quantity_packed` values.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/pack-response'
        '400':
          description: Pack is not in `processing` status, or quantity exceeds what was packed.
  /orders/packs/{packId}/complete:
    post:
      tags:
        - packs
      summary: Complete a pack
      operationId: complete-pack
      description: |-
        Completes the pack. This is the handoff from fulfillment to shipping (or to collection for click-and-collect orders).

        ## Behavior

        On completion, Carriyo:

        1. Moves all line items to `fulfilled` via `createFulfillment`.
        2. For each package with a `shipment_id`, marks the shipment as `ready_to_ship` and applies the `ship_zone` if provided.
        3. For fulfillment orders with `delivery_method = COLLECTION` and a `customer_collection_address`, auto-creates a **Collection** record in `open` status. No shipment is created for store-pickup collection FOs.

        The `ready_to_ship` transition fires the shipment-status webhook. That's the signal your systems receive at the fulfillment/shipping boundary.

        ## Validation

        - The pack must be in `processing` status.
        - Every item must be fully packed (`quantity_packed >= quantity`).
        - Every non-digital, non-collection package must have a `shipment_id` (created via Create shipment from pack).
        - If any package has a `shipment_id`, `ship_zone` is required in the request body.
      parameters:
        - name: packId
          in: path
          required: true
          schema:
            type: string
          description: The pack identifier.
      requestBody:
        required: false
        description: Optional. Required when the pack has shipments (to provide the ship zone).
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/complete-pack-request'
      responses:
        '200':
          description: Pack completed. Line items fulfilled, shipments ready to ship.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/pack-response'
        '400':
          description: Not all items packed, shipments missing, or ship zone required but not provided.
  /orders/packs/{packId}/cancel:
    post:
      tags:
        - packs
      summary: Cancel a pack
      operationId: cancel-pack
      description: |-
        Cancels the pack and reverts all side effects.

        ## Behavior

        On cancellation:

        1. All `pack_in_progress` line items revert to `picked` (if a `pick_id` was set on the pack item) or `allocated` (if no pick).
        2. Any active (non-cancelled) shipments on the pack's packages are cancelled via the shipment operations queue.
        3. Similar line items on the fulfillment order are merged back together.

        The request body is optional. If provided, `reason_code` is stored on the pack for auditing.

        ## Validation

        - The pack must not be in `completed` or `cancelled` status.
      parameters:
        - name: packId
          in: path
          required: true
          schema:
            type: string
          description: The pack identifier.
      requestBody:
        required: false
        description: Optional cancellation reason.
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/cancel-pack-request'
      responses:
        '200':
          description: Pack cancelled. Line items reverted, shipments cancelled.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/pack-response'
        '400':
          description: Pack is already completed or cancelled.
  /orders/packs/{packId}/create-shipment:
    post:
      tags:
        - packs
      summary: Create a shipment from the pack
      operationId: create-shipment-from-pack
      description: |-
        Books a shipment for one or more packages in the pack. All selected packages must belong to the same order and fulfillment order.

        ## Behavior

        Carriyo builds a shipment from the selected packages: each package becomes a parcel in the shipment, with its dimensions, weight, and items. The `shipment_id` is stored on each package.

        If a `carrier_account` is provided, the shipment is booked with that carrier. Otherwise, Carriyo uses the default carrier selection rules.

        ## Validation

        - The pack must be in `processing` status.
        - All packages must belong to the same order and fulfillment order.
        - Each package must have items (you must call Pack items first).
        - Store-pickup collection FOs are blocked. Collections are auto-created on pack complete instead.
      parameters:
        - name: packId
          in: path
          required: true
          schema:
            type: string
          description: The pack identifier.
      requestBody:
        required: true
        description: The packages to ship and optional carrier account.
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/create-pack-shipment-request'
            examples:
              with-carrier:
                summary: Ship with a specific carrier account
                value:
                  package_ids:
                    - PKG_456
                    - PKG_457
                  carrier_account:
                    carrier_id: d4aec947-7f13-4d1e-a1c1-ef9c467313d7
                    carrier_account_name: DHL Express - INT
      responses:
        '200':
          description: Shipment created. Packages now have `shipment_id` set.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/pack-response'
        '400':
          description: Packages empty, mixed FOs, or store-collection FO.
  /orders/collections/{collectionId}:
    get:
      tags:
        - collections
      summary: Get a collection
      operationId: get-collection
      description: |-
        Returns the full collection record, including packages, customer, verification state, and all timestamps.

        The list endpoints (`/order/{orderId}`, `/fulfillment-order/{foId}`, `/pack/{packId}`, `/shipment/{shipmentId}`) return **lookup records** with only summary fields. Use this endpoint for the complete data.
      parameters:
        - name: collectionId
          in: path
          required: true
          schema:
            type: string
          description: The collection identifier (e.g. `COL_123`).
      responses:
        '200':
          description: The full collection record.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/collection-response'
        '400':
          description: Collection not found.
  /orders/collections/order/{orderId}:
    get:
      tags:
        - collections
      summary: List collections for an order
      operationId: list-collections-for-order
      description: |-
        Returns all collections associated with an order. These are **lookup records** with only summary fields. Use Get collection for the full record.
      parameters:
        - name: orderId
          in: path
          required: true
          schema:
            type: string
          description: The order identifier.
      responses:
        '200':
          description: Array of collection summary records.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/collection-response'
  /orders/collections/fulfillment-order/{fulfillmentOrderId}:
    get:
      tags:
        - collections
      summary: List collections for a fulfillment order
      operationId: list-collections-for-fulfillment-order
      description: |-
        Returns all collections associated with a fulfillment order. These are **lookup records** with only summary fields. Use Get collection for the full record.
      parameters:
        - name: fulfillmentOrderId
          in: path
          required: true
          schema:
            type: string
          description: The fulfillment order identifier.
      responses:
        '200':
          description: Array of collection summary records.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/collection-response'
  /orders/collections/pack/{packId}:
    get:
      tags:
        - collections
      summary: List collections for a pack
      operationId: list-collections-for-pack
      description: |-
        Returns all collections created from a given pack. Use this to find which collections were auto-created when the pack completed.
      parameters:
        - name: packId
          in: path
          required: true
          schema:
            type: string
          description: The pack identifier.
      responses:
        '200':
          description: Array of collection summary records.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/collection-response'
  /orders/collections/shipment/{shipmentId}:
    get:
      tags:
        - collections
      summary: List collections for a shipment
      operationId: list-collections-for-shipment
      description: |-
        Returns all collections linked to a given shipment. Relevant for remote-collection scenarios where a shipment moves parcels to a different pickup location.
      parameters:
        - name: shipmentId
          in: path
          required: true
          schema:
            type: string
          description: The shipment identifier.
      responses:
        '200':
          description: Array of collection summary records.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/collection-response'
  /orders/collections/{collectionId}/ready:
    post:
      tags:
        - collections
      summary: Mark collection ready for pickup
      operationId: mark-collection-ready
      description: |-
        Transitions the collection from `open` to `ready_to_collect`. The parcel is at the collection point and the customer can be notified.

        ## Behavior

        On success, Carriyo sets `ready_date` and optionally schedules an auto-expire job. If `customer_collection_auto_expire_enabled` is on in the tenant's order management settings, the collection will automatically transition to `expired` after `customer_collection_auto_expire_days` days (default 7).

        ## Validation

        - The collection must be in `open` status.

        No request body is required.
      parameters:
        - name: collectionId
          in: path
          required: true
          schema:
            type: string
          description: The collection identifier.
      responses:
        '200':
          description: Collection is now `ready_to_collect`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/collection-response'
        '400':
          description: Collection is not in `open` status.
  /orders/collections/{collectionId}/reopen:
    post:
      tags:
        - collections
      summary: Reopen a collection
      operationId: reopen-collection
      description: |-
        Sends a `ready_to_collect` collection back to `open`. Use this when the parcel needs to go back to the warehouse (e.g. the customer rescheduled).

        ## Behavior

        Resets the verification state to `pending` and clears OTP data. Auto-expire and auto-cancel schedules are dropped.

        ## Validation

        - The collection must be in `ready_to_collect` status.

        No request body is required.
      parameters:
        - name: collectionId
          in: path
          required: true
          schema:
            type: string
          description: The collection identifier.
      responses:
        '200':
          description: Collection reopened. Status is now `open`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/collection-response'
        '400':
          description: Collection is not in `ready_to_collect` status.
  /orders/collections/{collectionId}/cancel:
    post:
      tags:
        - collections
      summary: Cancel a collection
      operationId: cancel-collection
      description: |-
        Cancels the collection. The request body is optional; if provided, `cancellation_reason` is stored for auditing.

        ## Behavior

        Cancellation clears any pending auto-expire or auto-cancel schedules and resets verification state.

        When a collection is cancelled (or collected), Carriyo checks whether **all** collections linked to each affected line item are now terminal (`collected` or `cancelled`). If so, the line item moves to `closed`, and the fulfillment order and order statuses are recomputed.

        ## Validation

        - The collection must not be in `collected` or `cancelled` status.
      parameters:
        - name: collectionId
          in: path
          required: true
          schema:
            type: string
          description: The collection identifier.
      requestBody:
        required: false
        description: Optional cancellation reason.
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/cancel-collection-request'
      responses:
        '200':
          description: Collection cancelled.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/collection-response'
        '400':
          description: Collection is already `collected` or `cancelled`.
  /orders/collections/{collectionId}/unexpire:
    post:
      tags:
        - collections
      summary: Reverse auto-expiry
      operationId: unexpire-collection
      description: |-
        Reverses an auto-expired collection back to `ready_to_collect`. Use this when the customer turns up late and you want to give them another pickup window.

        ## Behavior

        Drops the pending auto-cancel schedule (if any) and re-schedules a fresh auto-expire window.

        ## Validation

        - The collection must be in `expired` status.

        No request body is required.
      parameters:
        - name: collectionId
          in: path
          required: true
          schema:
            type: string
          description: The collection identifier.
      responses:
        '200':
          description: Collection is back to `ready_to_collect`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/collection-response'
        '400':
          description: Collection is not in `expired` status.
  /orders/collections/{collectionId}/verification/send-otp:
    post:
      tags:
        - collections
      summary: Send pickup OTP to customer
      operationId: send-collection-otp
      description: |-
        Generates a 6-digit OTP and sends it to the customer's email address. The OTP is hashed (SHA-256) before storage; the plaintext is never persisted.

        ## Behavior

        The response includes `masked_email` (e.g. `j***n@example.com`) and `otp_expires_at` so staff can tell the customer where to look and how long they have.

        ## OTP constraints

        - 6 digits.
        - Expires after 5 minutes.
        - Maximum 5 verification attempts before the OTP is invalidated (customer must request a new one).
        - 60-second cooldown between resends.

        ## Validation

        - The collection must be in `ready_to_collect` status.
        - The customer must have an email address on file.
        - At least 60 seconds must have passed since the last OTP was sent.

        No request body is required.
      parameters:
        - name: collectionId
          in: path
          required: true
          schema:
            type: string
          description: The collection identifier.
      responses:
        '200':
          description: OTP sent.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/send-otp-response'
        '400':
          description: Collection not in `ready_to_collect`, no customer email, or resend cooldown not elapsed.
  /orders/collections/{collectionId}/verification/verify-and-collect:
    post:
      tags:
        - collections
      summary: Verify OTP and complete handover
      operationId: verify-and-collect
      description: |-
        Verifies the OTP supplied by the customer and marks the collection as `collected`. This is the final handover step.

        ## Two modes

        **OTP verification:** Send `otp` in the request. Carriyo compares the SHA-256 hash against the stored hash. On match, the collection moves to `collected` with `verification.status = verified`.

        **Staff override:** Send `override: true` (no `otp` needed). The collection moves to `collected` with `verification.status = overridden`. Use this when the customer can prove identity but cannot access the OTP.

        ## Behavior on collect

        When a collection reaches `collected`, Carriyo checks each linked fulfillment-order line item. If **all** collections referenced by a line item are now terminal (`collected` or `cancelled`), the line item moves to `closed`. The fulfillment order and order statuses are then recomputed.

        ## Validation

        - The collection must be in `ready_to_collect` status.
        - An OTP must have been sent (unless using override).
        - The OTP must not be expired (5-minute window).
        - The maximum attempt count (5) must not be exceeded. Each failed attempt increments the counter.
      parameters:
        - name: collectionId
          in: path
          required: true
          schema:
            type: string
          description: The collection identifier.
      requestBody:
        required: true
        description: The OTP from the customer, or an override flag.
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/verify-and-collect-request'
            examples:
              otp-verify:
                summary: Verify with OTP
                value:
                  otp: "482917"
                  override: false
              staff-override:
                summary: Staff override without OTP
                value:
                  override: true
      responses:
        '200':
          description: Collection is now `collected`. Handover complete.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/collection-response'
        '400':
          description: Wrong OTP, expired OTP, max attempts exceeded, or collection not in `ready_to_collect`.
  /bin-locations/{locationId}:
    post:
      tags:
        - bin-locations
      summary: Create bin location
      description: Creates a bin-location mapping for an SKU at the given fulfillment location.
      operationId: create-bin-location
      parameters:
        - name: locationId
          in: path
          required: true
          schema:
            type: string
          description: Fulfillment location ID.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/bin-location-request'
      responses:
        '201':
          description: Created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/bin-location-response'
  /bin-locations/{locationId}/{sku}:
    get:
      tags:
        - bin-locations
      summary: Get bin location by SKU
      operationId: get-bin-location
      parameters:
        - name: locationId
          in: path
          required: true
          schema:
            type: string
          description: Fulfillment location ID.
        - name: sku
          in: path
          required: true
          schema:
            type: string
          description: SKU.
      responses:
        '200':
          description: Success.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/bin-location-response'
    put:
      tags:
        - bin-locations
      summary: Update bin location
      operationId: update-bin-location
      parameters:
        - name: locationId
          in: path
          required: true
          schema:
            type: string
          description: Fulfillment location ID.
        - name: sku
          in: path
          required: true
          schema:
            type: string
          description: SKU.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/bin-location-request'
      responses:
        '200':
          description: Success.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/bin-location-response'
    delete:
      tags:
        - bin-locations
      summary: Delete bin location
      operationId: delete-bin-location
      parameters:
        - name: locationId
          in: path
          required: true
          schema:
            type: string
          description: Fulfillment location ID.
        - name: sku
          in: path
          required: true
          schema:
            type: string
          description: SKU.
      responses:
        '204':
          description: Deleted.
  /bin-locations/{locationId}/{sku}/update-sku:
    put:
      tags:
        - bin-locations
      summary: Rename the SKU on a bin-location mapping
      description: Changes the SKU key on an existing bin-location mapping, preserving the bin assignment.
      operationId: rename-bin-location-sku
      parameters:
        - name: locationId
          in: path
          required: true
          schema:
            type: string
          description: Fulfillment location ID.
        - name: sku
          in: path
          required: true
          schema:
            type: string
          description: Current SKU on the mapping.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/update-sku-request'
      responses:
        '200':
          description: Success.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/bin-location-response'
  /bin-locations/{locationId}/bulk/import:
    post:
      tags:
        - bin-locations
      summary: Bulk import bin locations
      operationId: bulk-import-bin-locations
      parameters:
        - name: locationId
          in: path
          required: true
          schema:
            type: string
          description: Fulfillment location ID.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: array
              items:
                $ref: '#/components/schemas/bin-location-request'
      responses:
        '200':
          description: Success.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/bin-location-response'
      description: Bulk-imports an array of bin-location mappings for the given fulfillment location. Each item is processed independently and the response reports per-item outcomes.
components:
  securitySchemes:
    OAuth2-Production:
      type: oauth2
      flows:
        clientCredentials:
          tokenUrl: https://api.carriyo.com/oauth/token
          scopes: {}
    OAuth2-Demo:
      type: oauth2
      flows:
        clientCredentials:
          tokenUrl: https://demo-api.carriyo.com/oauth/token
          scopes: {}
    ApiKey:
      type: apiKey
      in: header
      name: x-api-key
      description: "Your tenant's API key, issued in the Carriyo Dashboard. Required on every request except `POST /oauth/token`."
    TenantId:
      type: apiKey
      in: header
      name: tenant-id
      description: "Your Carriyo tenant ID, shown in the Carriyo Dashboard. Required on every request except `POST /oauth/token`."
  schemas:
    pick-request:
      title: Create pick request
      type: object
      description: Request body for creating a pick work order.
      required:
        - location_id
        - items
      properties:
        location_id:
          type: string
          description: |-
            The fulfillment location where the pick takes place. All items in the request must belong to fulfillment orders allocated to this location.
          example: LOC_001
        picker:
          type: string
          description: |-
            The user to assign as the picker (typically an email or user ID). If omitted, Carriyo assigns a picker automatically using the location's configured strategy (`work_load` assigns the picker with the fewest active picks; `manual` leaves the pick unassigned until a picker claims it or you call reassign).

            When provided, the user must have `pick` permission for the location in Fulfillment App access settings.
        items:
          type: array
          minItems: 1
          description: The fulfillment-order line items to include in this pick. At least one item is required.
          items:
            $ref: '#/components/schemas/pick-item-request'
    pick-item-request:
      title: Pick item request
      type: object
      description: Identifies a single fulfillment-order line item to include in the pick.
      required:
        - order_id
        - fulfillment_order_id
        - line_item_id
        - quantity
      properties:
        order_id:
          type: string
          description: The order that owns this line item.
          example: ORD_1001
        fulfillment_order_id:
          type: string
          description: The fulfillment order that owns this line item. Must be allocated to the pick's `location_id`.
          example: FO_2001
        line_item_id:
          type: string
          description: The specific line item to pick. Must be in `allocated` status and not already assigned to another active pick.
          example: LI_3001
        quantity:
          type: integer
          minimum: 1
          description: |-
            How many units to pick. Must not exceed the line item's allocated quantity.

            If this is less than the line item's total quantity, Carriyo splits the line item: the original keeps the remaining quantity as `allocated`, and a new line item is created for the picked portion.
          example: 2
    pick-response:
      title: Pick
      type: object
      description: |-
        The pick work order. Contains the full lifecycle state, the list of items being picked, and any generated documents (e.g. the pick list PDF).

        **Lookup records** returned by the list endpoints (`/order/{orderId}`, `/fulfillment-order/{foId}`) contain only `tenant`, `pick_id`, `location_id`, `status`, and `creation_date`. Use Get pick for the full record.
      properties:
        tenant:
          type: string
          readOnly: true
          description: Tenant identifier.
        pick_id:
          type: string
          readOnly: true
          description: Unique pick identifier, prefixed `PIK_` (e.g. `PIK_142`).
          example: PIK_142
        location_id:
          type: string
          description: The fulfillment location this pick belongs to.
          example: LOC_001
        picker:
          type: string
          description: The assigned picker (email or user ID). May be `null` if picker assignment strategy is `manual` and no picker has been assigned yet.
        pick_type:
          type: string
          readOnly: true
          enum:
            - ORDER_PICK
            - ZONE_PICK
            - CLUSTER_PICK
          description: |-
            How Carriyo classified this pick based on the items provided at creation.

            - `ORDER_PICK`: all items belong to a single fulfillment order (the default).
            - `CLUSTER_PICK`: items span multiple fulfillment orders, all fully included. Requires `cluster_picking_enabled` in the location's Fulfillment App settings.
            - `ZONE_PICK`: items span multiple fulfillment orders, but only a subset of each FO's items. Requires `split_picking_enabled`.
        status:
          type: string
          readOnly: true
          enum:
            - open
            - processing
            - stopped
            - completed
            - cancelled
          description: |-
            The pick lifecycle state.

            - `open`: created, waiting for the picker to start.
            - `processing`: the picker is actively working.
            - `stopped`: the pick was cancelled while items were partially picked. The pick can be cancelled again to fully cancel and revert line items.
            - `completed`: all items accounted for; handover to packing is done. Terminal.
            - `cancelled`: the pick was voided. Terminal.
        creation_date:
          type: string
          format: date-time
          readOnly: true
          description: When the pick was created (ISO 8601).
        update_date:
          type: string
          format: date-time
          readOnly: true
          description: When the pick was last modified (ISO 8601).
        start_date:
          type: string
          format: date-time
          readOnly: true
          description: When the pick was started (`open` → `processing`). Null until started.
        cancel_date:
          type: string
          format: date-time
          readOnly: true
          description: When the pick was cancelled or stopped. Null if not cancelled.
        cancellation_reason_code:
          type: string
          readOnly: true
          description: The reason code provided when the pick was cancelled. Null if not cancelled or no reason was given.
        completed_date:
          type: string
          format: date-time
          readOnly: true
          description: When the pick was completed. Null until completed.
        items:
          type: array
          description: The line items in this pick with their picking progress. Only present on full pick records (not on lookup records from list endpoints).
          items:
            $ref: '#/components/schemas/pick-item'
        documents:
          type: array
          description: Documents generated for this pick (typically a pick-list PDF). Only present on full pick records.
          items:
            $ref: '#/components/schemas/fulfillment-document'
        creation_source:
          allOf:
            - $ref: '#/components/schemas/request-source'
          readOnly: true
          description: Which system or actor created this pick.
        update_source:
          allOf:
            - $ref: '#/components/schemas/request-source'
          readOnly: true
          description: Which system or actor last modified this pick.
    pick-item:
      title: Pick item
      type: object
      description: |-
        A line item within a pick, enriched with product metadata resolved at creation time. Tracks the picking progress via `quantity`, `quantity_picked`, and `mispicks`.
      properties:
        order_id:
          type: string
          description: The parent order identifier.
        merchant:
          type: string
          description: The merchant this item belongs to (relevant for multi-merchant tenants).
        fulfillment_order_id:
          type: string
          description: The parent fulfillment order identifier.
        partner_order_reference:
          type: string
          description: The merchant's own order reference (e.g. Shopify order number).
        partner_fulfillment_order_reference:
          type: string
          description: The merchant's own fulfillment order reference.
        line_item_id:
          type: string
          description: The fulfillment-order line item identifier.
        sku:
          type: string
          description: Product SKU, copied from the line item at pick creation.
        description:
          type: string
          description: Product description, copied from the line item.
        barcode:
          type: string
          description: Product barcode for scanner verification.
        image_link:
          type: string
          description: URL to the product image, for picker reference.
        product_ref:
          type: string
          description: External product reference.
        product_id:
          type: string
          description: Carriyo product identifier.
        weight:
          $ref: '#/components/schemas/weight'
        origin_country:
          type: string
          description: Country of origin (ISO 3166-1 alpha-2).
        hs_code:
          type: string
          description: Harmonized System code for customs.
        dangerous_goods:
          type: boolean
          description: Whether this item is classified as dangerous goods.
        unit_price:
          type: number
          description: Unit price of the item.
        bin_locations:
          type: array
          items:
            type: string
          description: |-
            The warehouse bin locations where this SKU is stored at the pick's location. Resolved from the bin-location mappings at pick creation. Guides the picker to the right shelf.
          example:
            - A-1-03
            - B-2-07
        quantity:
          type: integer
          description: The total quantity requested for this item in this pick.
          example: 3
        quantity_picked:
          type: integer
          description: |-
            How many units have been picked so far. Incremented by Pick items, decremented by Restock items. Starts at `0`.
          example: 2
        picked_timestamp:
          type: string
          format: date-time
          description: When the last pick action occurred on this item.
        selection_method:
          type: string
          enum:
            - SCANNER
            - CAMERA
            - MANUAL
          description: |-
            How the picker confirmed this item.

            - `SCANNER`: barcode scan.
            - `CAMERA`: photo recognition.
            - `MANUAL`: manual confirmation without scanning.
        mispicks:
          type: array
          description: Mispick records for this item. Each entry records a quantity that couldn't be picked and the reason.
          items:
            $ref: '#/components/schemas/mispick'
    mispick:
      title: Mispick
      type: object
      description: A record of items that could not be picked, with a reason.
      properties:
        quantity:
          type: integer
          description: Number of units that could not be picked.
          example: 1
        reason:
          type: string
          description: Free-text reason for the mispick (e.g. "Item damaged on shelf", "Out of stock", "Wrong item in bin").
          example: Item damaged on shelf
    cancel-pick-request:
      title: Cancel pick request
      type: object
      description: Optional request body for cancelling a pick.
      properties:
        reason_code:
          type: string
          description: A reason code for the cancellation, stored on the pick for auditing. Free-text.
          example: operator_reassigned
    reassign-pick-request:
      title: Reassign pick request
      type: object
      description: Request body for reassigning a pick to a different picker.
      required:
        - picker
      properties:
        picker:
          type: string
          description: The new picker to assign (email or user ID). Must have `pick` permission for the pick's location.
          example: picker2@example.com
    pick-item-action-request:
      title: Pick / restock item action
      type: object
      description: Identifies a pick item and the quantity to pick or restock. Used by both the pick-items and restock-items endpoints.
      required:
        - fulfillment_order_id
        - line_item_id
        - quantity
      properties:
        fulfillment_order_id:
          type: string
          description: The fulfillment order containing the line item.
          example: FO_2001
        line_item_id:
          type: string
          description: The line item to pick or restock.
          example: LI_3001
        quantity:
          type: integer
          minimum: 1
          description: |-
            Number of units to pick or restock.

            **For pick**: must not exceed `item.quantity - item.quantity_picked - sum(mispick quantities)`.

            **For restock**: must not exceed `item.quantity_picked`.
          example: 1
    mispick-item-request:
      title: Mispick item request
      type: object
      description: Records a mispick for a specific line item in the pick.
      required:
        - fulfillment_order_id
        - line_item_id
        - quantity
      properties:
        fulfillment_order_id:
          type: string
          description: The fulfillment order containing the line item.
          example: FO_2001
        line_item_id:
          type: string
          description: The line item that was mispicked.
          example: LI_3001
        quantity:
          type: integer
          minimum: 1
          description: Number of units that could not be picked. Must not exceed the remaining unpicked, un-mispicked quantity.
          example: 1
        reason:
          type: string
          description: Free-text reason explaining why the item couldn't be picked.
          example: Out of stock
    pack-request:
      title: Create pack request
      type: object
      description: Request body for creating a pack work order.
      required:
        - location_id
        - items
      properties:
        location_id:
          type: string
          description: The fulfillment location where packing takes place. All items must belong to fulfillment orders at this location.
          example: LOC_001
        packing_station:
          type: string
          description: The packing station where the work happens. Optional at creation, but required before the pack can be started.
          example: STATION_A
        packer:
          type: string
          description: The user doing the packing (email or user ID). Optional at creation, but required before the pack can be started. The user must have `pack` permission for the location.
          example: packer@example.com
        items:
          type: array
          minItems: 1
          description: The fulfillment-order line items to include in this pack. At least one item is required.
          items:
            $ref: '#/components/schemas/pack-item-request'
    pack-item-request:
      title: Pack item request
      type: object
      description: Identifies a single fulfillment-order line item to include in the pack.
      required:
        - order_id
        - fulfillment_order_id
        - line_item_id
        - quantity
      properties:
        order_id:
          type: string
          description: The order that owns this line item.
          example: ORD_1001
        fulfillment_order_id:
          type: string
          description: The fulfillment order that owns this line item. Must be allocated to the pack's `location_id`.
          example: FO_2001
        line_item_id:
          type: string
          description: |-
            The specific line item to pack. Must be in `allocated` or `picked` status and not already in another active pack.
          example: LI_3001
        pick_id:
          type: string
          description: |-
            Optional. The pick this item came from. When provided, the pick must be in `completed` status. Setting this links the pack to the pick and affects cancellation behavior: if the pack is cancelled, line items revert to `picked` instead of `allocated`.
          example: PIK_142
        quantity:
          type: integer
          minimum: 1
          description: |-
            How many units to pack. Must not exceed the line item's available quantity.

            If less than the line item's total, Carriyo splits the line item: the original keeps the remaining quantity, and a new line item is created for the packed portion.
          example: 2
    pack-response:
      title: Pack
      type: object
      description: |-
        The pack work order. Contains the lifecycle state, line items being packed, physical packages (parcels) being built, and any generated documents.

        **Lookup records** returned by the list endpoints (`/order/{orderId}`, `/fulfillment-order/{foId}`, `/pick/{pickId}`) contain only summary fields. Use Get pack for the full record.
      properties:
        tenant:
          type: string
          readOnly: true
          description: Tenant identifier.
        pack_id:
          type: string
          readOnly: true
          description: Unique pack identifier, prefixed `PAK_` (e.g. `PAK_87`).
          example: PAK_87
        location_id:
          type: string
          description: The fulfillment location this pack belongs to.
          example: LOC_001
        packing_station:
          type: string
          description: The packing station where this pack is being worked.
          example: STATION_A
        packer:
          type: string
          description: The assigned packer (email or user ID).
        status:
          type: string
          readOnly: true
          enum:
            - open
            - processing
            - completed
            - cancelled
          description: |-
            The pack lifecycle state.

            - `open`: created, waiting for packer to start.
            - `processing`: the packer is actively building parcels.
            - `completed`: all items packed, shipments booked, handed off. Terminal.
            - `cancelled`: the pack was voided, line items reverted. Terminal.
        creation_date:
          type: string
          format: date-time
          readOnly: true
          description: When the pack was created (ISO 8601).
        update_date:
          type: string
          format: date-time
          readOnly: true
          description: When the pack was last modified (ISO 8601).
        start_date:
          type: string
          format: date-time
          readOnly: true
          description: When the pack was started. Null until started.
        cancel_date:
          type: string
          format: date-time
          readOnly: true
          description: When the pack was cancelled. Null if not cancelled.
        cancellation_reason_code:
          type: string
          readOnly: true
          description: The reason code provided when the pack was cancelled.
        cancelled_by:
          allOf:
            - $ref: '#/components/schemas/request-source'
          readOnly: true
          description: Which system or actor cancelled this pack.
        completed_date:
          type: string
          format: date-time
          readOnly: true
          description: When the pack was completed. Null until completed.
        items:
          type: array
          description: The line items in this pack with packing progress. Only present on full pack records.
          items:
            $ref: '#/components/schemas/pack-item'
        packages:
          type: array
          description: The physical packages (parcels) being built. Each package can hold items from a single fulfillment order. Only present on full pack records.
          items:
            $ref: '#/components/schemas/pack-package'
        documents:
          type: array
          description: Documents generated for this pack (typically a packing-list PDF). Only present on full pack records.
          items:
            $ref: '#/components/schemas/fulfillment-document'
        creation_source:
          allOf:
            - $ref: '#/components/schemas/request-source'
          readOnly: true
          description: Which system or actor created this pack.
        update_source:
          allOf:
            - $ref: '#/components/schemas/request-source'
          readOnly: true
          description: Which system or actor last modified this pack.
    pack-item:
      title: Pack item
      type: object
      description: A line item within a pack, enriched with product metadata. Tracks packing progress via `quantity` and `quantity_packed`.
      properties:
        order_id:
          type: string
          description: The parent order identifier.
        fulfillment_order_id:
          type: string
          description: The parent fulfillment order identifier.
        partner_order_reference:
          type: string
          description: The merchant's own order reference.
        partner_fulfillment_order_reference:
          type: string
          description: The merchant's own fulfillment order reference.
        line_item_id:
          type: string
          description: The fulfillment-order line item identifier.
        merchant:
          type: string
          description: The merchant this item belongs to.
        pick_id:
          type: string
          description: The pick this item came from, if any. Affects cancellation behavior.
        product_ref:
          type: string
          description: External product reference.
        product_id:
          type: string
          description: Carriyo product identifier.
        sku:
          type: string
          description: Product SKU.
        description:
          type: string
          description: Product description.
        barcode:
          type: string
          description: Product barcode.
        image_link:
          type: string
          description: URL to the product image.
        weight:
          $ref: '#/components/schemas/weight'
        origin_country:
          type: string
          description: Country of origin (ISO 3166-1 alpha-2).
        hs_code:
          type: string
          description: Harmonized System code.
        dangerous_goods:
          type: boolean
          description: Whether this item is classified as dangerous goods.
        unit_price:
          type: number
          description: Unit price of the item.
        quantity:
          type: integer
          description: The total quantity to pack.
          example: 3
        quantity_packed:
          type: integer
          description: How many units have been placed into packages so far. Starts at `0`.
          example: 2
        packed_timestamp:
          type: string
          format: date-time
          description: When the last pack action occurred on this item.
        selection_method:
          type: string
          enum:
            - SCANNER
            - CAMERA
            - MANUAL
          description: How the packer confirmed this item (barcode scan, camera, or manual).
    pack-package:
      title: Pack package
      type: object
      description: |-
        A physical package (parcel) within the pack. Items are placed into packages via the pack-items endpoint. Each package typically becomes its own shipment.
      properties:
        package_id:
          type: string
          readOnly: true
          description: Unique package identifier, prefixed `PKG_` (e.g. `PKG_456`).
          example: PKG_456
        order_id:
          type: string
          description: The order this package belongs to.
        fulfillment_order_id:
          type: string
          description: The fulfillment order this package belongs to. All items in a package must come from the same FO.
        package_type:
          type: string
          description: The carton type, drawn from the merchant's configured packaging catalog.
          example: small_box
        dimension:
          $ref: '#/components/schemas/dimension'
        empty_weight:
          allOf:
            - $ref: '#/components/schemas/weight'
          description: Weight of the empty carton.
        max_weight:
          allOf:
            - $ref: '#/components/schemas/weight'
          description: Maximum weight capacity of this carton type.
        weight:
          allOf:
            - $ref: '#/components/schemas/weight'
          description: Current measured weight of the package (carton + contents).
        items:
          type: array
          description: The line items placed in this package with their packed quantities.
          items:
            $ref: '#/components/schemas/package-item'
        partner_order_reference:
          type: string
          description: The merchant's own order reference.
        partner_fulfillment_order_reference:
          type: string
          description: The merchant's own fulfillment order reference.
        shipment_id:
          type: string
          description: The shipment this package maps to. Set when a shipment is created from the pack via Create shipment from pack.
        previous_shipment_ids:
          type: array
          items:
            type: string
          description: Previous shipment IDs if the shipment was cancelled and re-created.
        packed_timestamp:
          type: string
          format: date-time
          description: When items were last placed into this package.
    package-item:
      title: Package item
      type: object
      description: A line item placed into a specific package, with its packed quantity.
      properties:
        line_item_id:
          type: string
          description: The fulfillment-order line item identifier.
        quantity:
          type: integer
          description: How many units of this line item are in this package.
          example: 2
    reassign-pack-request:
      title: Reassign pack request
      type: object
      description: Request body for reassigning a pack. At least one of `packing_station` or `packer` must be provided.
      properties:
        packing_station:
          type: string
          description: The new packing station. Must be a valid station for the location.
          example: STATION_B
        packer:
          type: string
          description: The new packer (email or user ID). Must have `pack` permission for the location.
          example: packer2@example.com
    add-package-request:
      title: Add package request
      type: object
      description: Request body for adding a new package to the pack.
      required:
        - order_id
        - fulfillment_order_id
      properties:
        order_id:
          type: string
          description: The order this package belongs to. Must match an item in the pack.
          example: ORD_1001
        fulfillment_order_id:
          type: string
          description: The fulfillment order this package belongs to. Must match an item in the pack.
          example: FO_2001
        package_type:
          type: string
          description: The carton type from the packaging catalog.
          example: medium_box
        dimension:
          $ref: '#/components/schemas/dimension'
        empty_weight:
          allOf:
            - $ref: '#/components/schemas/weight'
          description: Weight of the empty carton.
        max_weight:
          allOf:
            - $ref: '#/components/schemas/weight'
          description: Maximum weight capacity.
    update-package-request:
      title: Update package request
      type: object
      description: Request body for updating a package's physical properties. All fields are optional; only provided fields are updated.
      properties:
        package_type:
          type: string
          description: The carton type.
        dimension:
          $ref: '#/components/schemas/dimension'
        empty_weight:
          allOf:
            - $ref: '#/components/schemas/weight'
          description: Weight of the empty carton.
        max_weight:
          allOf:
            - $ref: '#/components/schemas/weight'
          description: Maximum weight capacity.
    pack-item-action-request:
      title: Pack item action
      type: object
      description: Identifies a line item and the package to place it into.
      required:
        - line_item_id
        - fulfillment_order_id
        - package_id
        - quantity
      properties:
        line_item_id:
          type: string
          description: The line item to pack.
          example: LI_3001
        fulfillment_order_id:
          type: string
          description: The fulfillment order the line item belongs to.
          example: FO_2001
        package_id:
          type: string
          description: The package to place the item into. Must belong to the same fulfillment order.
          example: PKG_456
        quantity:
          type: integer
          minimum: 1
          description: Number of units to place. Must not exceed `item.quantity - item.quantity_packed`.
          example: 2
        selection_method:
          type: string
          enum:
            - SCANNER
            - CAMERA
            - MANUAL
          description: How the packer confirmed this item.
    unpack-item-action-request:
      title: Unpack item action
      type: object
      description: Identifies a line item and the package to remove it from.
      required:
        - line_item_id
        - package_id
        - quantity
      properties:
        line_item_id:
          type: string
          description: The line item to unpack.
          example: LI_3001
        package_id:
          type: string
          description: The package to remove the item from.
          example: PKG_456
        quantity:
          type: integer
          minimum: 1
          description: Number of units to remove. Must not exceed the item's packed quantity in this package.
          example: 1
    complete-pack-request:
      title: Complete pack request
      type: object
      description: Optional request body for completing a pack. Required when the pack has booked shipments.
      properties:
        ship_zone:
          type: string
          description: |-
            The ship zone for the destination. **Required** when any package in the pack has a `shipment_id`. Applied to all shipments in the pack on completion.
          example: ZONE_A
    cancel-pack-request:
      title: Cancel pack request
      type: object
      description: Optional request body for cancelling a pack.
      properties:
        reason_code:
          type: string
          description: A reason code for the cancellation, stored for auditing. Free-text.
          example: wrong_items
    create-pack-shipment-request:
      title: Create shipment from pack request
      type: object
      description: Request body for creating a shipment from one or more packages in the pack.
      required:
        - package_ids
      properties:
        package_ids:
          type: array
          minItems: 1
          items:
            type: string
          description: The packages to include in the shipment. All must belong to the same order and fulfillment order, and each must have items.
          example:
            - PKG_456
            - PKG_457
        carrier_account:
          $ref: '#/components/schemas/carrier-account-request'
    collection-response:
      title: Collection
      type: object
      description: |-
        The customer-pickup runtime entity. Tracks a customer's order from "parcel is ready" through identity verification to handover.

        Collections are auto-created when a pack completes for a click-and-collect fulfillment order. There is no `POST /orders/collections` endpoint.

        **Lookup records** returned by list endpoints contain only summary fields. Use Get collection for the full record.
      properties:
        tenant:
          type: string
          readOnly: true
          description: Tenant identifier.
        collection_id:
          type: string
          readOnly: true
          description: Unique collection identifier, prefixed `COL_` (e.g. `COL_58`).
          example: COL_58
        status:
          type: string
          readOnly: true
          enum:
            - open
            - ready_to_collect
            - collected
            - expired
            - cancelled
          description: |-
            The collection lifecycle state.

            - `open`: created but not yet at the collection point.
            - `ready_to_collect`: at the collection point, customer can be notified and verified.
            - `collected`: handed over to the customer. Terminal (success).
            - `expired`: customer didn't collect within the configured window. Recoverable via unexpire.
            - `cancelled`: explicitly cancelled or auto-cancelled after expiry. Terminal.
        location_id:
          type: string
          description: The pickup location where the customer collects the parcel.
          example: LOC_001
        address:
          type: object
          description: The customer collection address (the pickup location's address). Uses the same `Address` structure as the fulfillment order's `customer_collection_address`.
        packages:
          type: array
          description: The packages available for pickup, copied from the pack that created this collection.
          items:
            $ref: '#/components/schemas/collection-package'
        customer:
          $ref: '#/components/schemas/collection-customer'
        verification:
          $ref: '#/components/schemas/collection-verification'
        customer_collection_schedule:
          type: object
          description: The customer's preferred collection schedule, copied from the fulfillment order.
        creation_date:
          type: string
          format: date-time
          readOnly: true
          description: When the collection was created (ISO 8601).
        update_date:
          type: string
          format: date-time
          readOnly: true
          description: When the collection was last modified (ISO 8601).
        ready_date:
          type: string
          format: date-time
          readOnly: true
          description: When the collection was marked ready for pickup. Null until ready.
        collected_date:
          type: string
          format: date-time
          readOnly: true
          description: When the collection was handed over. Null until collected.
        expiry_date:
          type: string
          format: date-time
          readOnly: true
          description: When the collection expired. Null if not expired.
        cancel_date:
          type: string
          format: date-time
          readOnly: true
          description: When the collection was cancelled. Null if not cancelled.
        cancellation_reason:
          type: string
          readOnly: true
          description: The reason provided when the collection was cancelled.
        expire_schedule_id:
          type: string
          readOnly: true
          description: Internal identifier for the auto-expire EventBridge schedule. Null if auto-expire is not active.
        cancel_schedule_id:
          type: string
          readOnly: true
          description: Internal identifier for the auto-cancel EventBridge schedule. Null if auto-cancel is not active.
        partner_order_reference:
          type: string
          description: The merchant's own order reference.
        partner_fulfillment_order_reference:
          type: string
          description: The merchant's own fulfillment order reference.
        pack_id:
          type: string
          readOnly: true
          description: The pack that created this collection.
          example: PAK_87
        notes:
          type: string
          description: Free-text notes on the collection.
        carriyo_metadata:
          type: array
          description: Custom key-value metadata entries.
          items:
            type: object
            properties:
              key:
                type: string
              value:
                type: string
    collection-package:
      title: Collection package
      type: object
      description: A package available for pickup, copied from the originating pack.
      properties:
        package_id:
          type: string
          description: The package identifier from the pack.
          example: PKG_456
        order_id:
          type: string
          description: The order this package belongs to.
        fulfillment_order_id:
          type: string
          description: The fulfillment order this package belongs to.
        partner_order_reference:
          type: string
          description: The merchant's own order reference.
        partner_fulfillment_order_reference:
          type: string
          description: The merchant's own fulfillment order reference.
        package_type:
          type: string
          description: The carton type.
        dimension:
          $ref: '#/components/schemas/dimension'
        weight:
          $ref: '#/components/schemas/weight'
        items:
          type: array
          description: The line items in this package.
          items:
            $ref: '#/components/schemas/package-item'
        shipment_id:
          type: string
          description: The shipment that moved this package to the collection point (for remote-collection scenarios). Null for local collection.
    collection-customer:
      title: Collection customer
      type: object
      description: The customer who will collect the order.
      properties:
        name:
          type: string
          description: Customer's full name.
          example: John Smith
        phone:
          type: string
          description: Customer's phone number.
          example: "+971501234567"
        email:
          type: string
          description: Customer's email address. Required for OTP verification (the OTP is sent here).
          example: john@example.com
    collection-verification:
      title: Collection verification
      type: object
      description: |-
        The OTP verification state for the collection. Tracks whether identity verification is pending, verified, or overridden.
      properties:
        method:
          type: string
          enum:
            - OTP
            - NONE
          description: The verification method configured for this collection.
        status:
          type: string
          enum:
            - pending
            - verified
            - overridden
          description: |-
            The current verification state.

            - `pending`: OTP not yet verified.
            - `verified`: OTP matched successfully.
            - `overridden`: staff bypassed OTP verification.
        otp_expires_at:
          type: string
          format: date-time
          description: When the current OTP expires (5 minutes after send). Null if no OTP has been sent.
        otp_sent_at:
          type: string
          format: date-time
          description: When the last OTP was sent.
        verified_at:
          type: string
          format: date-time
          description: When the OTP was verified or overridden. Null until verified.
        verified_by:
          type: string
          description: Who performed the verification (staff user ID for overrides).
    cancel-collection-request:
      title: Cancel collection request
      type: object
      description: Optional request body for cancelling a collection.
      properties:
        cancellation_reason:
          type: string
          description: Free-text reason for the cancellation, stored for auditing.
          example: customer_no_show
    send-otp-response:
      title: Send OTP response
      type: object
      description: Response after sending an OTP to the customer.
      properties:
        message:
          type: string
          description: Confirmation message.
          example: OTP sent successfully
        otp_sent_at:
          type: string
          format: date-time
          description: When the OTP was sent (ISO 8601).
        otp_expires_at:
          type: string
          format: date-time
          description: When the OTP expires (5 minutes after send).
        masked_email:
          type: string
          description: The customer's email with most characters masked, so staff can see where the code went without leaking the full address.
          example: "j***n@example.com"
    verify-and-collect-request:
      title: Verify and collect request
      type: object
      description: Request body for verifying the customer's OTP and completing the handover.
      properties:
        otp:
          type: string
          description: The 6-digit OTP from the customer. Required unless `override` is `true`.
          example: "482917"
        override:
          type: boolean
          default: false
          description: |-
            Set to `true` to bypass OTP verification. The collection moves to `collected` with `verification.status = overridden`. Use for staff exceptions when the customer can prove identity but cannot access the OTP.
    bin-location-request:
      title: Bin location request
      type: object
      required:
        - sku
        - bins
      properties:
        location_id:
          type: string
          description: The fulfillment location identifier. Usually inferred from the URL path; included on the body for bulk imports.
        sku:
          type: string
          description: Product SKU.
        bins:
          type: array
          items:
            type: string
          description: Bin identifiers in the warehouse where this SKU is stored at the given location.
    bin-location-response:
      title: Bin location response
      type: object
      properties:
        tenant_id:
          type: string
          description: Tenant identifier.
        location_id:
          type: string
          description: Fulfillment location identifier.
        sku:
          type: string
          description: Product SKU.
        bins:
          type: array
          items:
            type: string
          description: Bin identifiers in the warehouse where this SKU is stored.
        creation_date:
          type: string
          format: date-time
          description: Creation timestamp (ISO 8601).
        update_date:
          type: string
          format: date-time
          description: Last update timestamp (ISO 8601).
    update-sku-request:
      title: Update SKU request
      type: object
      required:
        - new_sku
      properties:
        new_sku:
          type: string
          description: The new SKU to assign to the existing bin mapping.
    weight:
      title: Weight
      type: object
      description: A weight measurement.
      properties:
        value:
          type: number
          description: The numeric weight value.
          example: 1.5
        unit:
          type: string
          description: Weight unit (`kg` or `lb`). Defaults to `kg` if not provided.
          enum:
            - kg
            - lb
          example: kg
    request-source:
      title: Request source
      type: object
      description: Identifies which system or actor triggered a create or update action.
      properties:
        source_type:
          type: string
          description: The type of source (e.g. `api`, `dashboard`, `storeapp`, `order_service`).
        source_id:
          type: string
          description: Identifier of the originator (e.g. user ID, connector identifier).
        api_pattern:
          type: string
          description: For API-originated requests, the matched API path pattern.
        request_type:
          type: string
          description: The Carriyo request type that produced this change (e.g. `PICK_CREATE`, `PICK_START`).
        system_log_identifier:
          type: string
          description: Reference to the system log entry that recorded this request.
    dimension:
      title: Dimension
      type: object
      description: Physical dimensions of a package.
      properties:
        width:
          type: number
          description: Width in the specified unit.
          example: 30
        height:
          type: number
          description: Height in the specified unit.
          example: 20
        depth:
          type: number
          description: Depth in the specified unit.
          example: 15
        unit:
          type: string
          description: Dimension unit (e.g. `cm`, `in`).
          enum:
            - cm
            - in
          example: cm
    carrier-account-request:
      title: Carrier account request
      type: object
      description: Identifies which carrier account to use for a shipment.
      properties:
        carrier_id:
          type: string
          description: The carrier identifier (a GUID from the carrier network configuration).
          example: d4aec947-7f13-4d1e-a1c1-ef9c467313d7
        carrier_account_name:
          type: string
          description: The specific carrier account name configured in Carriyo.
          example: DHL Express - INT
    fulfillment-document:
      title: Fulfillment document
      type: object
      description: A document attached to a pick or pack, such as an auto-generated pick list or packing list PDF.
      properties:
        document_id:
          type: string
          description: The document-setting identifier this document was created from.
        name:
          type: string
          description: Display name (e.g. "Pick List").
          example: Pick List
        type:
          type: string
          description: The kind of document. `pick_list` for picks, `packing_list` for packs.
          enum:
            - pick_list
            - packing_list
        source:
          type: string
          description: How the document was produced.
          enum:
            - carriyo
            - carrier
            - user
        format:
          type: string
          description: File format of the document.
          enum:
            - pdf
        url:
          type: string
          description: Pre-signed URL to download the document. URLs expire after the tenant's configured document-expiry period.
        carrier_upload_status:
          type: string
          description: Reflects the async carrier label upload status. Typically unset for pick list and packing list documents.
          enum:
            - pending
            - complete
            - error
        carrier_upload_date:
          type: string
          format: date-time
          description: Timestamp of when the document was uploaded to the carrier.
        message:
          type: string
          description: Status or error message associated with the document.
