openapi: 3.1.0
info:
  title: Linkable Order API
  version: 1.0.0
  description: Push orders to Shopify through the Linkable Order Queue.
    Authenticate with the Bearer token issued to your account.
servers:
  - url: https://api.eod-infinitee.com
security: []
components:
  securitySchemes:
    ClientToken:
      type: http
      scheme: bearer
      description: "Bearer token issued by Linkable. Pass in the `Authorization:
        Bearer <token>` header."
  schemas:
    Store:
      type: object
      properties:
        store_id:
          type: string
          format: uuid
        name:
          type: string
          example: My Store
        shop_domain:
          type: string
          example: my-store.myshopify.com
        status:
          type: string
          enum:
            - ACTIVE
            - INACTIVE
            - CREDENTIAL_ERROR
    Product:
      type: object
      properties:
        product_id:
          type: string
          format: uuid
        title:
          type: string
          example: Classic T-Shirt
        status:
          type: string
          enum:
            - ACTIVE
            - ARCHIVED
            - DRAFT
        variants:
          type: array
          items:
            type: object
            properties:
              variant_id:
                type: string
                format: uuid
                description: Use this as `variant_id` when creating an order.
              title:
                type: string
                example: Black / L
              sku:
                type: string
                example: TS-BLK-L
              price:
                type: string
                example: "199000"
    Error:
      type: object
      properties:
        error:
          type: string
          example: NOT_FOUND
        message:
          type: string
          example: Resource not found
        details:
          type: object
      required:
        - error
        - message
    Order:
      type: object
      properties:
        order_id:
          type: string
          format: uuid
        status:
          type: string
          enum:
            - PENDING
            - PROCESSING
            - SUCCESS
            - FAILED
        idempotency_key:
          type: string
          format: uuid
        shopify_order_id:
          type: string
          nullable: true
        shopify_order_number:
          type: string
          nullable: true
        duplicate:
          type: boolean
        queued_at:
          type: string
          format: date-time
    PodOrder:
      type: object
      properties:
        order_id:
          type: string
          format: uuid
        reference_id:
          type: string
          example: REF-001
        status:
          type: string
          enum:
            - PENDING
            - PROCESSING
            - SUCCESS
            - SYNCED
            - FULFILLED
            - FAILED
            - CANCELLED
        duplicate:
          type: boolean
        queue_position:
          type: integer
          nullable: true
        addon_service:
          type: array
          items:
            type: string
            enum:
              - carton_box
              - scan_label
          description: Dịch vụ bổ sung cấp đơn, dạng mảng (có thể có đồng thời carton_box
            và scan_label); mảng rỗng = không có add-on. Trước SOQ-546 trường
            này là chuỗi đơn hoặc null — client cũ cần đọc dạng mảng.
        pricing:
          type: object
          properties:
            subtotal:
              type: number
              example: 40.97
            shipping_total:
              type: number
              example: 8.39
            discount_amount:
              type: number
              example: 2.05
            grand_total:
              type: number
              example: 47.31
        created_at:
          type: string
          format: date-time
        created_via:
          type: string
          enum:
            - api
            - portal_form
            - portal_import
            - ops_import
          description: "Nguồn tạo đơn: api (token tích hợp), portal_form (form trên
            Portal), portal_import (import file trên Portal), ops_import (import
            nội bộ). Chỉ có ở GET /v1/orders/pod và /v1/orders/pod/{order_id}."
    PodOrderLineItem:
      type: object
      properties:
        line_item_id:
          type: string
          format: uuid
        quantity:
          type: integer
        sides:
          type: object
          additionalProperties:
            type: string
          nullable: true
        line_total:
          type: number
          nullable: true
        sku:
          type: string
          nullable: true
        title:
          type: string
          nullable: true
        image_url:
          type: string
          nullable: true
          description: "Ảnh sản phẩm của line item: ảnh riêng của variant, fallback về ảnh
            product, null nếu cả hai đều không có."
        addon_service:
          type: string
          nullable: true
          description: Dịch vụ bổ sung cấp line (fabric_applique, lace_applique_emb).
        line_addon_surcharge:
          type: number
          nullable: true
          description: Phụ phí add-on cấp line (tính = đơn giá × quantity).
    PodOrderDetail:
      type: object
      description: Chi tiết đơn POD bao gồm line items.
      allOf:
        - $ref: "#/components/schemas/PodOrder"
        - type: object
          properties:
            customer:
              type: object
              nullable: true
              properties:
                email:
                  type: string
                first_name:
                  type: string
                last_name:
                  type: string
                phone:
                  type: string
            shipping_address:
              type: object
              nullable: true
              properties:
                address1:
                  type: string
                address2:
                  type: string
                city:
                  type: string
                province:
                  type: string
                country_code:
                  type: string
                zip:
                  type: string
            line_items:
              type: array
              items:
                $ref: "#/components/schemas/PodOrderLineItem"
    Webhook:
      type: object
      description: A registered webhook endpoint. Never includes the signing secret.
      properties:
        id:
          type: string
          format: uuid
        url:
          type: string
          format: uri
          example: https://example.com/hooks/linkable
          description: HTTPS endpoint we POST events to.
        description:
          type: string
          nullable: true
          example: Production order sync
        topics:
          type: array
          items:
            type: string
            enum:
              - order.created
              - order.status_updated
              - order.cancelled
              - tracking.created
              - tracking.updated
          example:
            - order.created
            - order.status_updated
          description: Topics this endpoint is subscribed to.
        is_active:
          type: boolean
          description: False when disabled — either by you or auto-disabled after 10
            consecutive delivery failures.
        consecutive_failures:
          type: integer
          example: 0
          description: Deliveries that have failed in a row; reset to 0 on any success.
        disabled_reason:
          type: string
          nullable: true
          example: too_many_failures
          description: Why the endpoint was disabled, when `is_active` is false.
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      required:
        - id
        - url
        - description
        - topics
        - is_active
        - consecutive_failures
        - disabled_reason
        - created_at
        - updated_at
    WebhookWithSecret:
      type: object
      description: A webhook plus its plaintext signing `secret`. Returned ONCE — on
        create and on rotate-secret. Store it now; it is never shown again.
      allOf:
        - $ref: "#/components/schemas/Webhook"
        - type: object
          properties:
            secret:
              type: string
              example: whsec_a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4
              description: HMAC-SHA256 signing secret. Use it to verify the
                `X-Webhook-Signature` header.
          required:
            - secret
    WebhookTopic:
      type: object
      description: A subscribable topic and its description.
      properties:
        topic:
          type: string
          enum:
            - order.created
            - order.status_updated
            - order.cancelled
            - tracking.created
            - tracking.updated
          example: order.created
        description:
          type: string
          example: An order was created.
      required:
        - topic
        - description
    WebhookDelivery:
      type: object
      description: A single delivery attempt log for one event to one webhook.
      properties:
        id:
          type: string
          format: uuid
        webhook_id:
          type: string
          format: uuid
        event_id:
          type: string
          format: uuid
          description: The event id — matches `X-Webhook-Id` and the envelope `id`.
        topic:
          type: string
          example: order.created
          description: The event topic (or `webhook.test` for a manual test ping).
        payload:
          allOf:
            - $ref: "#/components/schemas/WebhookEventEnvelope"
          description: The exact envelope that was (or will be) POSTed.
        status:
          type: string
          enum:
            - pending
            - success
            - failed
        attempts:
          type: integer
          example: 1
          description: Number of delivery attempts made so far.
        last_status_code:
          type: integer
          nullable: true
          example: 200
          description: HTTP status of the most recent attempt, or null if none completed.
        last_error:
          type: string
          nullable: true
          example: null
          description: Error detail from the most recent failed attempt.
        next_retry_at:
          type: string
          format: date-time
          nullable: true
          description: When the next retry is scheduled, or null when not pending a retry.
        delivered_at:
          type: string
          format: date-time
          nullable: true
          description: When the delivery first succeeded, or null.
        created_at:
          type: string
          format: date-time
      required:
        - id
        - webhook_id
        - event_id
        - topic
        - payload
        - status
        - attempts
        - last_status_code
        - last_error
        - next_retry_at
        - delivered_at
        - created_at
    WebhookEventEnvelope:
      type: object
      description: The JSON body we POST to your endpoint. `data` shape depends on
        `topic` — order/tracking topics carry an OrderEventData.
      properties:
        id:
          type: string
          format: uuid
          description: Event id — identical across every webhook that receives this event;
            use it to dedupe retries.
        topic:
          type: string
          example: order.created
        created_at:
          type: string
          format: date-time
          description: ISO-8601 timestamp of when the event was emitted.
        data:
          description: Event payload. An OrderEventData for order/tracking topics.
          oneOf:
            - $ref: "#/components/schemas/OrderEventData"
            - type: object
              additionalProperties: true
      required:
        - id
        - topic
        - created_at
        - data
    OrderEventData:
      type: object
      description: Client-facing snapshot of an order carried by every order/tracking event.
      properties:
        order:
          type: object
          properties:
            id:
              type: string
              format: uuid
            reference_id:
              type: string
              example: REF-10023
            store_id:
              type: string
              format: uuid
            client_status:
              type: string
              nullable: true
              example: shipping
              description: Client-facing status key, or null when none is set yet.
            shipment_status:
              type: string
              nullable: true
              example: in_transit
              description: Shipment status derived from carrier tracking, or null when not
                shipping yet.
            created_at:
              type: string
              format: date-time
            updated_at:
              type: string
              format: date-time
          required:
            - id
            - reference_id
            - store_id
            - client_status
            - shipment_status
            - created_at
            - updated_at
        tracking:
          type: object
          nullable: true
          description: Tracking snapshot; null when the order has no tracking number yet.
          properties:
            tracking_number:
              type: string
              nullable: true
              example: 1Z999AA10123456784
            carrier:
              type: string
              nullable: true
              example: UPS
            tracking_url:
              type: string
              format: uri
              nullable: true
              example: https://www.ups.com/track?tracknum=1Z999AA10123456784
            shipment_status:
              type: string
              nullable: true
              example: in_transit
          required:
            - tracking_number
            - carrier
            - tracking_url
            - shipment_status
        previous:
          type: object
          additionalProperties: true
          description: Only present on `*_updated` topics — the subset of fields that
            changed.
      required:
        - order
        - tracking
    FeatureFlag:
      type: object
      description: Global feature flag state.
      properties:
        key:
          type: string
          example: orders.create
        label:
          type: string
          example: Create Orders
        description:
          type: string
          example: Allow clients to create new orders via the API
        registry_default:
          type: boolean
          description: Hardcoded default in the registry
        default_enabled:
          type: boolean
          description: Current effective default (global override > registry)
        source:
          type: string
          enum:
            - registry
            - global
          description: Where the current default comes from
        updated_by:
          type: string
          nullable: true
        updated_at:
          type: string
          format: date-time
          nullable: true
    ClientFeature:
      type: object
      description: Feature state resolved for a specific client.
      properties:
        key:
          type: string
          example: orders.create
        label:
          type: string
          example: Create Orders
        description:
          type: string
          example: Allow clients to create new orders via the API
        enabled:
          type: boolean
          description: Whether this feature is enabled for the client
        source:
          type: string
          enum:
            - client
            - global
            - default
          description: "Resolution level: client override > global override > registry
            default"
        default_enabled:
          type: boolean
          description: Registry default value
    ClientFeaturesResponse:
      type: object
      description: Simple feature map for client portal (no source/note leaked).
      properties:
        features:
          type: object
          additionalProperties:
            type: boolean
          description: Map of feature key to enabled state
          example:
            orders.create: true
            orders.import: true
            orders.cancel: true
            orders.retry: true
            catalog.products: true
            wallet.view: true
            wallet.topup: true
            tickets: true
            webhooks: true
            api_tokens: true
tags:
  - name: Stores
    description: Browse available stores.
  - name: Products
    description: Browse products and get variant IDs for order creation.
  - name: Orders
    description: Create orders and track their delivery status.
  - name: pod-integration
    description: Print-on-Demand order API — tạo đơn POD, xem trạng thái, retry/cancel.
  - name: config
    description: Reference config — pricing sides, shipping rates, yarn types,
      country codes, special surcharges, carton box capacity.
  - name: Webhooks
    description: Register endpoints to receive signed event notifications (orders,
      tracking) instead of polling. Manage endpoints and inspect deliveries
      here; see the guide at /api-reference/webhooks for signature verification
      and the event payloads.
  - name: Client Portal
    description: Client portal endpoints for self-service operations.
paths:
  /v1/stores:
    get:
      operationId: listStores
      tags:
        - Stores
      summary: List stores
      description: Returns all active stores available to your account. Use the
        `store_id` to browse products.
      security:
        - ClientToken: []
      responses:
        "200":
          description: List of stores.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/Store"
  /v1/stores/{store_id}/products:
    get:
      operationId: listProducts
      tags:
        - Products
      summary: List products
      description: Returns products and their variants for a store. Use `variant_id`
        from a variant when creating an order.
      security:
        - ClientToken: []
      parameters:
        - name: store_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: q
          in: query
          schema:
            type: string
          description: 'Search query. Interpreted according to `search_type`: product
            title (default), product_id (UUID), shopify_id, or variant SKU.
            Example: `?q=HOODIE&search_type=sku` finds products with a variant
            SKU containing "HOODIE" (e.g. "emb - HOODIE - MAROON-L"). SKU search
            needs at least 3 characters.'
        - name: search_type
          in: query
          schema:
            type: string
            enum:
              - name
              - product_id
              - shopify_id
              - sku
            default: name
          description: "How to interpret `q`: `name` (ILIKE on title), `product_id` (exact
            UUID), `shopify_id` (exact Shopify product ID), `sku` (partial,
            case-insensitive match on variant SKU; `q` must be at least 3
            characters, otherwise 400)."
        - name: status
          in: query
          schema:
            type: string
            enum:
              - ACTIVE
              - ARCHIVED
              - DRAFT
        - name: limit
          in: query
          schema:
            type: integer
            default: 20
            maximum: 100
        - name: cursor
          in: query
          schema:
            type: string
          description: Pagination cursor from previous response.
      responses:
        "200":
          description: Paginated list of products with variants.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/Product"
                  next_cursor:
                    type: string
                    nullable: true
  /v1/stores/{store_id}/products/{product_id}:
    get:
      operationId: getProduct
      tags:
        - Products
      summary: Get product detail
      description: Returns a single product with all its variants. Use `variant_id`
        from a variant when creating an order.
      security:
        - ClientToken: []
      parameters:
        - name: store_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: product_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Product detail with variants.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Product"
        "404":
          description: Product not found.
  /v1/orders:
    post:
      operationId: createOrder
      tags:
        - Orders
      summary: Create an order
      description: >-
        Push a new order into the queue. The order will be delivered to Shopify
        automatically with rate-limit protection.


        Use `idempotency_key` to safely retry without creating duplicates.
      security:
        - ClientToken: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - idempotency_key
                - customer
                - shipping_address
                - line_items
              properties:
                idempotency_key:
                  type: string
                  format: uuid
                  description: Unique key per order — reuse the same key to safely retry without
                    creating a duplicate.
                customer:
                  type: object
                  required:
                    - email
                    - first_name
                    - last_name
                  properties:
                    email:
                      type: string
                      format: email
                      example: nguyen@example.com
                    first_name:
                      type: string
                      example: Van
                    last_name:
                      type: string
                      example: Nguyen
                    phone:
                      type: string
                      example: "+84901234567"
                shipping_address:
                  type: object
                  required:
                    - address1
                    - city
                    - province
                    - country_code
                    - zip
                  properties:
                    address1:
                      type: string
                      example: 123 Le Loi
                    address2:
                      type: string
                      example: Apt 4B
                    city:
                      type: string
                      example: Ho Chi Minh City
                    province:
                      type: string
                      example: Ho Chi Minh
                    country_code:
                      type: string
                      minLength: 2
                      maxLength: 2
                      example: VN
                    zip:
                      type: string
                      example: "700000"
                line_items:
                  type: array
                  minItems: 1
                  maxItems: 250
                  items:
                    type: object
                    required:
                      - variant_id
                      - quantity
                    properties:
                      variant_id:
                        type: string
                        format: uuid
                        description: Variant UUID provided by Linkable.
                      quantity:
                        type: integer
                        minimum: 1
                        example: 2
                note:
                  type: string
                  description: Optional order note.
                tags:
                  type: string
                  description: Comma-separated Shopify order tags.
                force:
                  type: boolean
                  default: false
                  description: Set `true` to bypass soft duplicate detection based on content
                    hash.
      responses:
        "200":
          description: Order accepted and queued for delivery to Shopify.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Order"
        "401":
          description: Missing or invalid Bearer token.
        "409":
          description: "Duplicate: `DUPLICATE_REFERENCE_ID` (reference_id đã tồn tại) hoặc
            `POTENTIAL_DUPLICATE` (nội dung giống — pass `force: true` để bỏ
            qua)."
        "422":
          description: Validation error — check request body fields.
    get:
      operationId: listOrders
      tags:
        - Orders
      summary: List orders
      description: Retrieve a paginated list of your orders. Results are scoped to
        your client token.
      security:
        - ClientToken: []
      parameters:
        - name: status
          in: query
          schema:
            type: string
            enum:
              - PENDING
              - PROCESSING
              - SUCCESS
              - FAILED
        - name: from
          in: query
          schema:
            type: string
            format: date-time
        - name: to
          in: query
          schema:
            type: string
            format: date-time
        - name: limit
          in: query
          schema:
            type: integer
            default: 20
            maximum: 100
        - name: cursor
          in: query
          schema:
            type: string
          description: Pagination cursor from previous response.
      responses:
        "200":
          description: Paginated list of orders.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/Order"
                  next_cursor:
                    type: string
                    nullable: true
  /v1/orders/{order_id}:
    get:
      operationId: getOrder
      tags:
        - Orders
      summary: Get order status
      description: Retrieve the current status and details of a specific order
        including line items and delivery attempts.
      security:
        - ClientToken: []
      parameters:
        - name: order_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Order detail with line items and delivery attempts.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Order"
        "404":
          description: Order not found.
  /v1/orders/pod:
    post:
      operationId: createPodOrder
      tags:
        - pod-integration
      summary: Create POD order
      description: >-
        Tạo đơn hàng Print-on-Demand. Hệ thống tự validate, tính pricing, và
        push sang Shopify.


        `reference_id` là mã đơn **duy nhất trong mỗi store** — gửi trùng ref sẽ
        bị từ chối với lỗi `409 DUPLICATE_REFERENCE_ID` (không tạo mới, không
        trả order cũ).
      security:
        - ClientToken: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - customer
                - line_items
              properties:
                reference_id:
                  type: string
                  description: Mã đơn hàng của bạn (tuỳ chọn, tối đa 100 ký tự). Bỏ trống thì đơn
                    không có ref và không bị so trùng. Có ref thì duy nhất trong
                    store — gửi trùng sẽ bị từ chối (409
                    DUPLICATE_REFERENCE_ID).
                  example: ORD-20250624-001
                shipping_service:
                  type: string
                  enum:
                    - eco
                    - premium
                  default: eco
                label_link:
                  type: string
                  format: uri
                  description: URL label tự cung cấp (TikTok, Etsy, ...). Có field này thì bỏ
                    shipping_address. Phải đi kèm tracking_number.
                tracking_number:
                  type: string
                  description: Tracking number đi kèm label_link.
                customer:
                  type: object
                  required:
                    - email
                    - first_name
                    - last_name
                  properties:
                    email:
                      type: string
                      format: email
                      example: john@gmail.com
                    first_name:
                      type: string
                      example: John
                    last_name:
                      type: string
                      example: Doe
                    phone:
                      type: string
                      example: "+14155551234"
                shipping_address:
                  type: object
                  description: Địa chỉ nhận hàng. Không cần khi đã cung cấp label_link.
                  required:
                    - address1
                    - city
                    - province
                    - country_code
                    - zip
                  properties:
                    address1:
                      type: string
                      example: 123 Main St
                    address2:
                      type: string
                      example: Apt 4B
                    city:
                      type: string
                      example: Los Angeles
                    province:
                      type: string
                      example: CA
                    country_code:
                      type: string
                      minLength: 2
                      maxLength: 2
                      example: US
                    zip:
                      type: string
                      example: "90001"
                line_items:
                  type: array
                  minItems: 1
                  maxItems: 250
                  items:
                    type: object
                    required:
                      - variant_id
                      - quantity
                      - sides
                    properties:
                      variant_id:
                        type: string
                        format: uuid
                        description: UUID sản phẩm (lấy từ Products API).
                      quantity:
                        type: integer
                        minimum: 1
                        example: 2
                      sides:
                        type: object
                        description: "Design URL cho mỗi mặt in. Keys: front_side, front_large, back,
                          pocket_chest_left, pocket_chest_right, left_sleeve,
                          right_sleeve, special_location."
                        additionalProperties:
                          type: string
                          format: uri
                        example:
                          front_side: https://cdn.example.com/design-front.png
                          back: https://cdn.example.com/design-back.png
                      printing_technique:
                        type: string
                        enum:
                          - DTG
                          - DTF
                        description: Bắt buộc cho printing.
                      no_printing_file:
                        type: boolean
                        description: "Khách không có file in: +$2/sản phẩm. Không áp dụng cho nón
                          (cap-like) và tote."
                      emb_file:
                        type: string
                        enum:
                          - Available
                          - No EMB file
                        description: Bắt buộc cho embroidery.
                      design_emb_type:
                        type: string
                        enum:
                          - text only
                          - vector / cartoon artwork
                          - Photo to Embroidery
                      number_of_pets:
                        type: integer
                        minimum: 1
                        maximum: 4
                        description: Bắt buộc khi Photo to Embroidery.
                      yarn_type:
                        type: string
                        enum:
                          - yarn_6_7mm
                          - yarn_8_10mm
                          - yarn_20mm
                          - plat_emb
                          - emb_3d_puff
                      addon_service:
                        type: string
                        enum:
                          - fabric_applique
                          - lace_applique_emb
                        description: "Dịch vụ bổ sung cấp line-item, tính theo quantity: fabric_applique
                          ($1.50/item) hoặc lace_applique_emb ($2.00/item)."
                      note:
                        type: string
                        description: Ghi chú cho sản phẩm.
                addon_service:
                  oneOf:
                    - type: array
                      items:
                        type: string
                        enum:
                          - carton_box
                          - scan_label
                      example:
                        - carton_box
                        - scan_label
                    - type: string
                      enum:
                        - carton_box
                        - scan_label
                  description: Dịch vụ bổ sung cấp đơn hàng, nhận mảng
                    (["carton_box","scan_label"]) hoặc chuỗi đơn (tương thích
                    ngược, được chuẩn hoá về mảng); giá trị trùng bị bỏ, mảng
                    rỗng = không add-on. carton_box (tính phí theo box,
                    items/box phụ thuộc category và store), scan_label ($1/đơn
                    flat). Phí addon_surcharge là tổng các add-on đã chọn.
                note:
                  type: string
                  description: Ghi chú đơn hàng.
                tags:
                  type: string
                  description: Tags (comma-separated).
                force:
                  type: boolean
                  default: false
                  description: Set `true` để bỏ qua kiểm tra trùng nội dung.
      responses:
        "200":
          description: Order accepted and queued for delivery to Shopify.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PodOrder"
        "400":
          description: Validation error — kiểm tra request body.
        "401":
          description: Missing or invalid Bearer token.
        "409":
          description: >-
            Trùng đơn:

            - `DUPLICATE_REFERENCE_ID` — `reference_id` đã tồn tại trong store
            (đổi ref khác để tạo đơn mới).

            - `POTENTIAL_DUPLICATE` — nội dung giống một đơn trong dedup window;
            gửi lại với `force: true` để bỏ qua.


            Body có `error` (code) và `details.existing_order`.
    get:
      operationId: listPodOrders
      tags:
        - pod-integration
      summary: List POD orders
      description: Danh sách đơn POD của bạn, hỗ trợ cursor pagination và filter theo
        status.
      security:
        - ClientToken: []
      parameters:
        - name: status
          in: query
          schema:
            type: string
            enum:
              - PENDING
              - PROCESSING
              - SUCCESS
              - SYNCED
              - FULFILLED
              - FAILED
              - CANCELLED
        - name: from
          in: query
          schema:
            type: string
            format: date-time
        - name: to
          in: query
          schema:
            type: string
            format: date-time
        - name: limit
          in: query
          schema:
            type: integer
            default: 20
            maximum: 100
        - name: cursor
          in: query
          schema:
            type: string
          description: Pagination cursor from previous response.
      responses:
        "200":
          description: Paginated list of POD orders.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/PodOrder"
                  next_cursor:
                    type: string
                    nullable: true
  /v1/orders/pod/{order_id}:
    get:
      operationId: getPodOrder
      tags:
        - pod-integration
      summary: Get POD order
      description: Chi tiết đơn POD bao gồm line items, pricing breakdown, và delivery
        attempts.
      security:
        - ClientToken: []
      parameters:
        - name: order_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: POD order detail.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PodOrderDetail"
        "404":
          description: Order not found.
  /v1/orders/pod/{order_id}/retry:
    post:
      operationId: retryPodOrder
      tags:
        - pod-integration
      summary: Retry failed POD order
      description: Retry đơn FAILED — đưa lại vào queue để push sang Shopify. Tối đa 5
        lần retry.
      security:
        - ClientToken: []
      parameters:
        - name: order_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Order queued for retry.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PodOrder"
        "400":
          description: Order is not in FAILED status or max retries exceeded.
        "404":
          description: Order not found.
  /v1/orders/pod/{order_id}/cancel:
    post:
      operationId: cancelPodOrder
      tags:
        - pod-integration
      summary: Cancel POD order
      description: Hủy đơn đang ở trạng thái PENDING hoặc FAILED.
      security:
        - ClientToken: []
      parameters:
        - name: order_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Order cancelled. `warnings` is present only when the cancel left
            something outstanding — most often a shipping label the carrier
            refused to void, which stays live and billable.
          content:
            application/json:
              schema:
                type: object
                properties:
                  order_id:
                    type: string
                    format: uuid
                  status:
                    type: string
                    enum:
                      - CANCELLED
                  warnings:
                    type: array
                    items:
                      type: string
                    description: Human-readable notes about what the cancel could not finish.
                required:
                  - order_id
                  - status
        "400":
          description: Order cannot be cancelled (not PENDING or FAILED).
        "404":
          description: Order not found.
  /v1/orders/pod/{order_id}/shipping:
    get:
      operationId: getPodOrderShipping
      tags:
        - pod-integration
      summary: Get shipping label for a POD order
      description: Nhãn vận chuyển đã mua cho đơn. Trả `null` khi chưa có nhãn còn
        hiệu lực — đơn chưa mua, mua hỏng, hoặc nhãn đã bị huỷ.
      security:
        - ClientToken: []
      parameters:
        - name: order_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Shipping label, or null when the order has none.
          content:
            application/json:
              schema:
                type: object
                nullable: true
                properties:
                  carrier:
                    type: string
                    nullable: true
                  tracking_number:
                    type: string
                    nullable: true
                  label_url:
                    type: string
                    description: Link tới storage của carrier. Ưu tiên dùng `/shipping/file` thay
                      cho link này — link carrier có thể hết hạn hoặc sập, còn
                      nhãn thì không cấp lại được, chỉ mua lại được.
                  label_format:
                    type: string
                    nullable: true
                    example: PDF
                  label_purchased_at:
                    type: string
                    format: date-time
                    nullable: true
                  fulfilled_at:
                    type: string
                    format: date-time
                    nullable: true
                  is_voided:
                    type: boolean
        "404":
          description: Order not found.
  /v1/orders/pod/{order_id}/shipping/file:
    get:
      operationId: getPodOrderShippingFile
      tags:
        - pod-integration
      summary: Download the shipping label file
      description: "Chính file nhãn. Trả bản sao trong storage của hệ thống khi có, và
        chỉ lấy từ carrier khi chưa có bản sao — nên endpoint này vẫn chạy sau
        khi link carrier hết hạn. Header `X-Label-Source` cho biết bản nào đã
        trả lời: `archive` hoặc `provider`."
      security:
        - ClientToken: []
      parameters:
        - name: order_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: The label file.
          headers:
            X-Label-Source:
              description: Which copy served the request.
              schema:
                type: string
                enum:
                  - archive
                  - provider
          content:
            application/pdf:
              schema:
                type: string
                format: binary
            image/png:
              schema:
                type: string
                format: binary
        "404":
          description: Order not found, or it has no live shipping label.
  /v1/config/pricing-sides:
    get:
      operationId: getPricingSides
      tags:
        - config
      summary: Get pricing sides config
      description: Bảng phụ phí mặt in theo category. Dùng để tính trước giá đơn hàng.
      security:
        - ClientToken: []
      responses:
        "200":
          description: Pricing sides configuration.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        category:
                          type: string
                        side_code:
                          type: string
                        surcharge:
                          type: number
  /v1/config/shipping-rates:
    get:
      operationId: getShippingRates
      tags:
        - config
      summary: Get shipping rates
      description: Bảng phí ship theo category và service type (eco/premium).
      security:
        - ClientToken: []
      responses:
        "200":
          description: Shipping rates.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        category:
                          type: string
                        service_type:
                          type: string
                        base_rate:
                          type: number
                        additional_rate:
                          type: number
  /v1/config/yarn-types:
    get:
      operationId: getYarnTypes
      tags:
        - config
      summary: Get yarn types
      description: Loại chỉ thêu và phí bổ sung.
      security:
        - ClientToken: []
      responses:
        "200":
          description: Yarn types configuration.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        category:
                          type: string
                        type_code:
                          type: string
                        extra_cost:
                          type: number
  /v1/config/country-codes:
    get:
      operationId: getCountryCodes
      tags:
        - config
      summary: Get valid country codes
      description: Danh sách quốc gia hợp lệ cho shipping_address.country_code.
      security:
        - ClientToken: []
      responses:
        "200":
          description: Country codes list.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        code:
                          type: string
                          example: US
                        name:
                          type: string
                          example: United States
  /v1/config/special-surcharges:
    get:
      operationId: getSpecialSurcharges
      tags:
        - config
      summary: Get special surcharges
      description: Bảng phụ phí đặc biệt (DTG, EMB, addon services).
      security:
        - ClientToken: []
      responses:
        "200":
          description: Special surcharges.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        surcharge_type:
                          type: string
                        condition_key:
                          type: string
                        amount:
                          type: number
  /v1/config/carton-box-config:
    get:
      operationId: getCartonBoxConfig
      tags:
        - config
      summary: Get carton box capacity config
      description: "Cấu hình số items/box theo category và store. Dùng để tính phí
        carton box add-on: $4 (box đầu) + $1/box thêm. Số box = ceil(items /
        items_per_box) tính riêng cho mỗi category."
      security:
        - ClientToken: []
      parameters:
        - name: store_id
          in: query
          schema:
            type: string
            format: uuid
          description: Filter theo store (optional). Nếu không truyền, trả tất cả stores.
      responses:
        "200":
          description: Carton box capacity config.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                          format: uuid
                        store_id:
                          type: string
                          format: uuid
                        store_name:
                          type: string
                        category:
                          type: string
                          example: tshirt
                        items_per_box:
                          type: integer
                          example: 5
                          description: Số sản phẩm tối đa mỗi box cho category này.
  /v1/me/webhooks/topics:
    get:
      operationId: listWebhookTopics
      tags:
        - Webhooks
      summary: List available topics
      description: List the event topics you can subscribe a webhook to, with a short
        description of each.
      security:
        - ClientToken: []
      responses:
        "200":
          description: The subscribable topics.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/WebhookTopic"
        "401":
          description: Missing or invalid Bearer token (`UNAUTHORIZED`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /v1/me/webhooks:
    get:
      operationId: listWebhooks
      tags:
        - Webhooks
      summary: List your webhooks
      description: List every webhook endpoint registered for your account. Signing
        secrets are never returned here.
      security:
        - ClientToken: []
      responses:
        "200":
          description: Your webhook endpoints.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/Webhook"
        "401":
          description: Missing or invalid Bearer token (`UNAUTHORIZED`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    post:
      operationId: createWebhook
      tags:
        - Webhooks
      summary: Register a webhook
      description: Register a new webhook endpoint. The response includes the
        plaintext signing `secret` **once** — store it now; it is never returned
        again. A client may register at most 5 webhooks.
      security:
        - ClientToken: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - url
                - topics
              properties:
                url:
                  type: string
                  format: uri
                  maxLength: 2048
                  example: https://example.com/hooks/linkable
                  description: HTTPS endpoint (must resolve to a public address).
                topics:
                  type: array
                  minItems: 1
                  items:
                    type: string
                    enum:
                      - order.created
                      - order.status_updated
                      - order.cancelled
                      - tracking.created
                      - tracking.updated
                  example:
                    - order.created
                    - order.status_updated
                  description: One or more topics to subscribe to. Must be unique.
                description:
                  type: string
                  maxLength: 1000
                  example: Production order sync
      responses:
        "201":
          description: Webhook created. `secret` is present here only.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookWithSecret"
        "400":
          description: Bad request — one of `VALIDATION_ERROR` (invalid body/params),
            `INVALID_WEBHOOK_URL` (scheme/SSRF check failed),
            `WEBHOOK_LIMIT_REACHED` (max 5 webhooks per client), or
            `WEBHOOK_INACTIVE` (test send on a disabled endpoint).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "401":
          description: Missing or invalid Bearer token (`UNAUTHORIZED`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /v1/me/webhooks/{id}:
    patch:
      operationId: updateWebhook
      tags:
        - Webhooks
      summary: Update a webhook
      description: "Update a webhook. Provide at least one field. Setting `is_active:
        true` re-activates an auto-disabled endpoint and clears its failure
        counter."
      security:
        - ClientToken: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: Webhook id.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              minProperties: 1
              properties:
                url:
                  type: string
                  format: uri
                  maxLength: 2048
                topics:
                  type: array
                  minItems: 1
                  items:
                    type: string
                    enum:
                      - order.created
                      - order.status_updated
                      - order.cancelled
                      - tracking.created
                      - tracking.updated
                description:
                  type: string
                  maxLength: 1000
                  nullable: true
                is_active:
                  type: boolean
      responses:
        "200":
          description: The updated webhook.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Webhook"
        "400":
          description: Bad request — one of `VALIDATION_ERROR` (invalid body/params),
            `INVALID_WEBHOOK_URL` (scheme/SSRF check failed),
            `WEBHOOK_LIMIT_REACHED` (max 5 webhooks per client), or
            `WEBHOOK_INACTIVE` (test send on a disabled endpoint).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "401":
          description: Missing or invalid Bearer token (`UNAUTHORIZED`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: Webhook or delivery not found (`NOT_FOUND`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    delete:
      operationId: deleteWebhook
      tags:
        - Webhooks
      summary: Delete a webhook
      description: Permanently delete a webhook endpoint.
      security:
        - ClientToken: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: Webhook id.
      responses:
        "204":
          description: Deleted.
        "401":
          description: Missing or invalid Bearer token (`UNAUTHORIZED`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: Webhook or delivery not found (`NOT_FOUND`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /v1/me/webhooks/{id}/rotate-secret:
    post:
      operationId: rotateWebhookSecret
      tags:
        - Webhooks
      summary: Rotate signing secret
      description: Issue a new signing secret for a webhook, invalidating the old one.
        The new plaintext `secret` is returned **once**.
      security:
        - ClientToken: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: Webhook id.
      responses:
        "200":
          description: The webhook with its new `secret`.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookWithSecret"
        "401":
          description: Missing or invalid Bearer token (`UNAUTHORIZED`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: Webhook or delivery not found (`NOT_FOUND`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /v1/me/webhooks/{id}/test:
    post:
      operationId: testWebhook
      tags:
        - Webhooks
      summary: Send a test event
      description: Send a one-off `webhook.test` event to this endpoint (regardless of
        its subscribed topics) so you can confirm your endpoint receives and
        verifies our signature. The endpoint must be active.
      security:
        - ClientToken: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: Webhook id.
      responses:
        "202":
          description: Test delivery queued.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookDelivery"
        "400":
          description: Bad request — one of `VALIDATION_ERROR` (invalid body/params),
            `INVALID_WEBHOOK_URL` (scheme/SSRF check failed),
            `WEBHOOK_LIMIT_REACHED` (max 5 webhooks per client), or
            `WEBHOOK_INACTIVE` (test send on a disabled endpoint).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "401":
          description: Missing or invalid Bearer token (`UNAUTHORIZED`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: Webhook or delivery not found (`NOT_FOUND`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /v1/me/webhooks/{id}/deliveries:
    get:
      operationId: listWebhookDeliveries
      tags:
        - Webhooks
      summary: List recent deliveries
      description: List recent delivery attempts for one webhook, most recent first.
      security:
        - ClientToken: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: Webhook id.
        - name: page
          in: query
          schema:
            type: integer
            minimum: 1
            default: 1
          description: 1-based page number.
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
      responses:
        "200":
          description: Paginated delivery log.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/WebhookDelivery"
                  page:
                    type: integer
                  limit:
                    type: integer
        "400":
          description: Bad request — one of `VALIDATION_ERROR` (invalid body/params),
            `INVALID_WEBHOOK_URL` (scheme/SSRF check failed),
            `WEBHOOK_LIMIT_REACHED` (max 5 webhooks per client), or
            `WEBHOOK_INACTIVE` (test send on a disabled endpoint).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "401":
          description: Missing or invalid Bearer token (`UNAUTHORIZED`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: Webhook or delivery not found (`NOT_FOUND`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /v1/me/webhooks/deliveries/{delivery_id}/redeliver:
    post:
      operationId: redeliverWebhookDelivery
      tags:
        - Webhooks
      summary: Re-queue a delivery
      description: Reset a delivery to a fresh attempt cycle and re-enqueue it.
      security:
        - ClientToken: []
      parameters:
        - name: delivery_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: Delivery id.
      responses:
        "202":
          description: Delivery re-queued.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookDelivery"
        "401":
          description: Missing or invalid Bearer token (`UNAUTHORIZED`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: Webhook or delivery not found (`NOT_FOUND`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /v1/storage-plans:
    get:
      operationId: listStoragePlans
      tags:
        - Client Portal
      summary: List storage plans
      description: Active storage plans for comparison (no prices).
      security:
        - ClientToken: []
      responses:
        "200":
          description: Active plans, ordered by `sort_order`.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        code:
                          type: string
                        name:
                          type: string
                        description:
                          type: string
                          nullable: true
                        storage_limit_bytes:
                          type: integer
                          nullable: true
                          description: null = unlimited.
                        max_file_size_bytes:
                          type: integer
                        sort_order:
                          type: integer
  /v1/design-files:
    get:
      operationId: listDesignFiles
      tags:
        - Client Portal
      summary: List your design files
      description: Design files stored by the authenticated client, newest first, with
        storage usage. Never includes other clients' files.
      security:
        - ClientToken: []
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
        - name: offset
          in: query
          schema:
            type: integer
            minimum: 0
            default: 0
      responses:
        "200":
          description: Page of design files plus `usage`.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        url:
                          type: string
                          format: uri
                          description: "Public file URL:
                            `DESIGN_FILES_PUBLIC_URL/design-files/{client_id}/{\
                            file}` when the dedicated bucket is configured,
                            otherwise
                            `{API_BASE_URL}/v1/design-files/{client_id}/{file}`.
                            When link signing is on it ends with
                            `?exp={unixMs}&sig={hmac}`: the link expires after
                            90 days and an invalid or expired one answers 404.
                            Call this list again to get a fresh link. Use the
                            URL as-is; do not strip the query."
                        path:
                          type: string
                          description: Path relative to the API base (`design-files/{client_id}/{file}`);
                            use it for DELETE.
                        file_name:
                          type: string
                        size:
                          type: integer
                        content_type:
                          type: string
                        created_at:
                          type: string
                          format: date-time
                  pagination:
                    type: object
                    properties:
                      limit:
                        type: integer
                      offset:
                        type: integer
                      total:
                        type: integer
                  usage:
                    type: object
                    description: Storage usage in bytes. `file_count` is for display only — files
                      are limited by bytes, never by count.
                    properties:
                      used_bytes:
                        type: integer
                        example: 52428800
                      limit_bytes:
                        type: integer
                        nullable: true
                        description: null = unlimited.
                        example: 1073741824
                      file_count:
                        type: integer
                        example: 12
                      plan:
                        type: object
                        nullable: true
                        properties:
                          code:
                            type: string
                            example: free
                          name:
                            type: string
                            example: Free
                          storage_limit_bytes:
                            type: integer
                            nullable: true
                          max_file_size_bytes:
                            type: integer
                            example: 20971520
    post:
      operationId: uploadDesignFile
      tags:
        - Client Portal
      summary: Upload a design file
      description: "Multipart upload (`file`: pdf, png, jpg, jpeg, emb; max 20MB).
        Storage is limited by bytes according to the client's storage plan,
        never by number of files. If used + new file size exceeds the plan's
        storage the request fails with 403 `STORAGE_LIMIT_REACHED` (`details`:
        `{used_bytes, limit_bytes, plan}`); a file larger than the plan's
        per-file limit fails with 413 `FILE_TOO_LARGE`. Nothing is stored in
        either case."
      security:
        - ClientToken: []
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                file:
                  type: string
                  format: binary
              required:
                - file
      responses:
        "201":
          description: Stored. Returns `data` (url, path, file_name, content_type, size)
            plus `usage`.
        "400":
          description: Missing file or invalid format.
        "403":
          description: Storage plan full — `STORAGE_LIMIT_REACHED`, details `{used_bytes,
            limit_bytes, plan}`.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "413":
          description: File larger than the plan's per-file limit — `FILE_TOO_LARGE`.
  /v1/design-files/{client_id}/{file}:
    delete:
      operationId: deleteDesignFile
      tags:
        - Client Portal
      summary: Delete a design file
      description: Removes the file and frees its bytes. Only the owning client can delete.
      security:
        - ClientToken: []
      parameters:
        - name: client_id
          in: path
          required: true
          schema:
            type: string
        - name: file
          in: path
          required: true
          schema:
            type: string
      responses:
        "204":
          description: Deleted.
        "404":
          description: Not found.
  /v1/me/features:
    get:
      operationId: getMyFeatures
      tags:
        - Client Portal
      summary: Get my feature flags
      description: Get the current client's enabled features. Returns a simple map of
        feature key to boolean — no source/note information is leaked.
      security:
        - ClientToken: []
      responses:
        "200":
          description: Feature flags for the current client.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ClientFeaturesResponse"
        "401":
          description: Missing or invalid credentials (`UNAUTHORIZED`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
webhooks:
  order.created:
    post:
      operationId: onOrderCreated
      summary: order.created
      description: 'An order was created. We POST a signed `WebhookEventEnvelope` with
        `topic: "order.created"` to your registered endpoint.'
      parameters:
        - name: Content-Type
          in: header
          required: true
          schema:
            type: string
            enum:
              - application/json
        - name: User-Agent
          in: header
          schema:
            type: string
            example: ShopifyOrderQueue-Webhook/1.0
        - name: X-Webhook-Id
          in: header
          required: true
          schema:
            type: string
            format: uuid
          description: The event id (matches the envelope `id`); stable across retries.
        - name: X-Webhook-Topic
          in: header
          required: true
          schema:
            type: string
            example: order.created
        - name: X-Webhook-Timestamp
          in: header
          required: true
          schema:
            type: string
            example: "1700000000"
          description: Unix seconds; part of the signed string. Reject stale requests.
        - name: X-Webhook-Signature
          in: header
          required: true
          schema:
            type: string
            example: sha256=<hex>
          description: HMAC-SHA256 over `"<X-Webhook-Timestamp>.<raw request body>"` using
            your signing secret, hex-encoded and prefixed `sha256=`.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/WebhookEventEnvelope"
            example:
              id: 11111111-1111-1111-1111-111111111111
              topic: order.created
              created_at: 2026-01-01T00:00:00.000Z
              data:
                order:
                  id: 33333333-3333-3333-3333-333333333333
                  reference_id: REF-10023
                  store_id: 44444444-4444-4444-4444-444444444444
                  client_status: shipping
                  shipment_status: in_transit
                  created_at: 2026-01-01T00:00:00.000Z
                  updated_at: 2026-01-01T00:05:00.000Z
                tracking: null
      responses:
        2xx:
          description: Any 2xx acknowledges receipt — the delivery is marked successful
            and not retried.
        default:
          description: Any non-2xx response (or no response within 10s) is treated as a
            failure and retried on the backoff schedule 1m, 5m, 30m, 2h, 6h, 12h
            (7 attempts total). An endpoint that fails 10 deliveries in a row is
            auto-disabled.
  order.status_updated:
    post:
      operationId: onOrderStatus_updated
      summary: order.status_updated
      description: "An order's status changed. We POST a signed `WebhookEventEnvelope`
        with `topic: \"order.status_updated\"` to your registered endpoint."
      parameters:
        - name: Content-Type
          in: header
          required: true
          schema:
            type: string
            enum:
              - application/json
        - name: User-Agent
          in: header
          schema:
            type: string
            example: ShopifyOrderQueue-Webhook/1.0
        - name: X-Webhook-Id
          in: header
          required: true
          schema:
            type: string
            format: uuid
          description: The event id (matches the envelope `id`); stable across retries.
        - name: X-Webhook-Topic
          in: header
          required: true
          schema:
            type: string
            example: order.status_updated
        - name: X-Webhook-Timestamp
          in: header
          required: true
          schema:
            type: string
            example: "1700000000"
          description: Unix seconds; part of the signed string. Reject stale requests.
        - name: X-Webhook-Signature
          in: header
          required: true
          schema:
            type: string
            example: sha256=<hex>
          description: HMAC-SHA256 over `"<X-Webhook-Timestamp>.<raw request body>"` using
            your signing secret, hex-encoded and prefixed `sha256=`.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/WebhookEventEnvelope"
            example:
              id: 11111111-1111-1111-1111-111111111111
              topic: order.status_updated
              created_at: 2026-01-01T00:00:00.000Z
              data:
                order:
                  id: 33333333-3333-3333-3333-333333333333
                  reference_id: REF-10023
                  store_id: 44444444-4444-4444-4444-444444444444
                  client_status: shipping
                  shipment_status: in_transit
                  created_at: 2026-01-01T00:00:00.000Z
                  updated_at: 2026-01-01T00:05:00.000Z
                tracking: null
      responses:
        2xx:
          description: Any 2xx acknowledges receipt — the delivery is marked successful
            and not retried.
        default:
          description: Any non-2xx response (or no response within 10s) is treated as a
            failure and retried on the backoff schedule 1m, 5m, 30m, 2h, 6h, 12h
            (7 attempts total). An endpoint that fails 10 deliveries in a row is
            auto-disabled.
  order.cancelled:
    post:
      operationId: onOrderCancelled
      summary: order.cancelled
      description: 'An order was cancelled. We POST a signed `WebhookEventEnvelope`
        with `topic: "order.cancelled"` to your registered endpoint.'
      parameters:
        - name: Content-Type
          in: header
          required: true
          schema:
            type: string
            enum:
              - application/json
        - name: User-Agent
          in: header
          schema:
            type: string
            example: ShopifyOrderQueue-Webhook/1.0
        - name: X-Webhook-Id
          in: header
          required: true
          schema:
            type: string
            format: uuid
          description: The event id (matches the envelope `id`); stable across retries.
        - name: X-Webhook-Topic
          in: header
          required: true
          schema:
            type: string
            example: order.cancelled
        - name: X-Webhook-Timestamp
          in: header
          required: true
          schema:
            type: string
            example: "1700000000"
          description: Unix seconds; part of the signed string. Reject stale requests.
        - name: X-Webhook-Signature
          in: header
          required: true
          schema:
            type: string
            example: sha256=<hex>
          description: HMAC-SHA256 over `"<X-Webhook-Timestamp>.<raw request body>"` using
            your signing secret, hex-encoded and prefixed `sha256=`.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/WebhookEventEnvelope"
            example:
              id: 11111111-1111-1111-1111-111111111111
              topic: order.cancelled
              created_at: 2026-01-01T00:00:00.000Z
              data:
                order:
                  id: 33333333-3333-3333-3333-333333333333
                  reference_id: REF-10023
                  store_id: 44444444-4444-4444-4444-444444444444
                  client_status: shipping
                  shipment_status: in_transit
                  created_at: 2026-01-01T00:00:00.000Z
                  updated_at: 2026-01-01T00:05:00.000Z
                tracking: null
      responses:
        2xx:
          description: Any 2xx acknowledges receipt — the delivery is marked successful
            and not retried.
        default:
          description: Any non-2xx response (or no response within 10s) is treated as a
            failure and retried on the backoff schedule 1m, 5m, 30m, 2h, 6h, 12h
            (7 attempts total). An endpoint that fails 10 deliveries in a row is
            auto-disabled.
  tracking.created:
    post:
      operationId: onTrackingCreated
      summary: tracking.created
      description: 'A tracking number was assigned to an order. We POST a signed
        `WebhookEventEnvelope` with `topic: "tracking.created"` to your
        registered endpoint.'
      parameters:
        - name: Content-Type
          in: header
          required: true
          schema:
            type: string
            enum:
              - application/json
        - name: User-Agent
          in: header
          schema:
            type: string
            example: ShopifyOrderQueue-Webhook/1.0
        - name: X-Webhook-Id
          in: header
          required: true
          schema:
            type: string
            format: uuid
          description: The event id (matches the envelope `id`); stable across retries.
        - name: X-Webhook-Topic
          in: header
          required: true
          schema:
            type: string
            example: tracking.created
        - name: X-Webhook-Timestamp
          in: header
          required: true
          schema:
            type: string
            example: "1700000000"
          description: Unix seconds; part of the signed string. Reject stale requests.
        - name: X-Webhook-Signature
          in: header
          required: true
          schema:
            type: string
            example: sha256=<hex>
          description: HMAC-SHA256 over `"<X-Webhook-Timestamp>.<raw request body>"` using
            your signing secret, hex-encoded and prefixed `sha256=`.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/WebhookEventEnvelope"
            example:
              id: 11111111-1111-1111-1111-111111111111
              topic: tracking.created
              created_at: 2026-01-01T00:00:00.000Z
              data:
                order:
                  id: 33333333-3333-3333-3333-333333333333
                  reference_id: REF-10023
                  store_id: 44444444-4444-4444-4444-444444444444
                  client_status: shipping
                  shipment_status: in_transit
                  created_at: 2026-01-01T00:00:00.000Z
                  updated_at: 2026-01-01T00:05:00.000Z
                tracking:
                  tracking_number: 1Z999AA10123456784
                  carrier: UPS
                  tracking_url: https://www.ups.com/track?tracknum=1Z999AA10123456784
                  shipment_status: in_transit
      responses:
        2xx:
          description: Any 2xx acknowledges receipt — the delivery is marked successful
            and not retried.
        default:
          description: Any non-2xx response (or no response within 10s) is treated as a
            failure and retried on the backoff schedule 1m, 5m, 30m, 2h, 6h, 12h
            (7 attempts total). An endpoint that fails 10 deliveries in a row is
            auto-disabled.
  tracking.updated:
    post:
      operationId: onTrackingUpdated
      summary: tracking.updated
      description: 'A shipment tracking update was received. We POST a signed
        `WebhookEventEnvelope` with `topic: "tracking.updated"` to your
        registered endpoint.'
      parameters:
        - name: Content-Type
          in: header
          required: true
          schema:
            type: string
            enum:
              - application/json
        - name: User-Agent
          in: header
          schema:
            type: string
            example: ShopifyOrderQueue-Webhook/1.0
        - name: X-Webhook-Id
          in: header
          required: true
          schema:
            type: string
            format: uuid
          description: The event id (matches the envelope `id`); stable across retries.
        - name: X-Webhook-Topic
          in: header
          required: true
          schema:
            type: string
            example: tracking.updated
        - name: X-Webhook-Timestamp
          in: header
          required: true
          schema:
            type: string
            example: "1700000000"
          description: Unix seconds; part of the signed string. Reject stale requests.
        - name: X-Webhook-Signature
          in: header
          required: true
          schema:
            type: string
            example: sha256=<hex>
          description: HMAC-SHA256 over `"<X-Webhook-Timestamp>.<raw request body>"` using
            your signing secret, hex-encoded and prefixed `sha256=`.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/WebhookEventEnvelope"
            example:
              id: 11111111-1111-1111-1111-111111111111
              topic: tracking.updated
              created_at: 2026-01-01T00:00:00.000Z
              data:
                order:
                  id: 33333333-3333-3333-3333-333333333333
                  reference_id: REF-10023
                  store_id: 44444444-4444-4444-4444-444444444444
                  client_status: shipping
                  shipment_status: in_transit
                  created_at: 2026-01-01T00:00:00.000Z
                  updated_at: 2026-01-01T00:05:00.000Z
                tracking:
                  tracking_number: 1Z999AA10123456784
                  carrier: UPS
                  tracking_url: https://www.ups.com/track?tracknum=1Z999AA10123456784
                  shipment_status: in_transit
      responses:
        2xx:
          description: Any 2xx acknowledges receipt — the delivery is marked successful
            and not retried.
        default:
          description: Any non-2xx response (or no response within 10s) is treated as a
            failure and retried on the backoff schedule 1m, 5m, 30m, 2h, 6h, 12h
            (7 attempts total). An endpoint that fails 10 deliveries in a row is
            auto-disabled.
  webhook.test:
    post:
      operationId: onWebhookTest
      summary: webhook.test
      description: 'A manual test ping triggered via the test endpoint. We POST a
        signed `WebhookEventEnvelope` with `topic: "webhook.test"` to your
        registered endpoint.'
      parameters:
        - name: Content-Type
          in: header
          required: true
          schema:
            type: string
            enum:
              - application/json
        - name: User-Agent
          in: header
          schema:
            type: string
            example: ShopifyOrderQueue-Webhook/1.0
        - name: X-Webhook-Id
          in: header
          required: true
          schema:
            type: string
            format: uuid
          description: The event id (matches the envelope `id`); stable across retries.
        - name: X-Webhook-Topic
          in: header
          required: true
          schema:
            type: string
            example: webhook.test
        - name: X-Webhook-Timestamp
          in: header
          required: true
          schema:
            type: string
            example: "1700000000"
          description: Unix seconds; part of the signed string. Reject stale requests.
        - name: X-Webhook-Signature
          in: header
          required: true
          schema:
            type: string
            example: sha256=<hex>
          description: HMAC-SHA256 over `"<X-Webhook-Timestamp>.<raw request body>"` using
            your signing secret, hex-encoded and prefixed `sha256=`.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/WebhookEventEnvelope"
            example:
              id: 11111111-1111-1111-1111-111111111111
              topic: webhook.test
              created_at: 2026-01-01T00:00:00.000Z
              data:
                message: This is a test event from Shopify Order Queue.
                webhook_id: 22222222-2222-2222-2222-222222222222
      responses:
        2xx:
          description: Any 2xx acknowledges receipt — the delivery is marked successful
            and not retried.
        default:
          description: Any non-2xx response (or no response within 10s) is treated as a
            failure and retried on the backoff schedule 1m, 5m, 30m, 2h, 6h, 12h
            (7 attempts total). An endpoint that fails 10 deliveries in a row is
            auto-disabled.
