openapi: 3.1.0
info:
  title: Balaji Customer API
  version: 1.0.0
  summary: Read your orders, shipments, invoices and catalog, and place orders, from your own systems.
  description: |
    The Balaji Customer API gives a wholesale account programmatic access to its own data. A credential belongs to one Balaji account. Reads cover that account and its sub-accounts. You never send a customer number.

    `/api/v1` is additive only: Balaji adds fields and routes but never removes or renames one. A breaking change ships as `/api/v2` with six months' notice, and both versions run side by side.
  contact:
    name: Balaji Integrations
    email: integrations@balajiwireless.com
servers:
  - url: https://shop.balajibrands.com
    description: Production. Test keys and live keys use the same host.
security:
  - bearerKey: []
tags:
  - name: Catalog
  - name: Orders
  - name: Shipments
  - name: Finance
  - name: Ship-tos
  - name: Webhooks
  - name: Partner OAuth
paths:
  /api/v1/items:
    get:
      tags: [Catalog]
      operationId: lookupItems
      summary: Look up parts
      description: |
        Look up items by `sku` and/or `upc` (comma lists, 50 identifiers together at most), or search with `q`. You see only the items your account can order, at your own price. Name `sku`/`upc`, or `q`, not both. `cursor` pages a `q` search only.

        Each key reads 2,000 item rows a day (Pacific time). Rows returned count; `not_found` entries do not.
      x-scope: catalog:read
      parameters:
        - name: sku
          in: query
          schema: { type: string }
          description: Comma list of Balaji SKUs.
          example: CL-CASE-IPH16-BK,CL-CASE-IPH16-BL
        - name: upc
          in: query
          schema: { type: string }
          description: Comma list of barcodes, 8 to 14 digits each.
        - name: q
          in: query
          schema: { type: string, minLength: 2, maxLength: 100 }
          description: Search text.
        - name: cursor
          in: query
          schema: { type: string }
          description: The `next_cursor` of a previous `q` page.
      responses:
        "200":
          description: Items, and the identifiers that matched no orderable item.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success: { const: true }
                  data: { $ref: "#/components/schemas/ItemsResult" }
              example:
                success: true
                data:
                  items:
                    - sku: CL-CASE-IPH16-BK
                      upc: "012345678905"
                      name: Clear Case iPhone 16
                      color: Black
                      brand: CLICK
                      series: Clear Series
                      device: iPhone 16
                      image_url: https://www.balajiwireless.com/images/cl-case-iph16-bk.jpg
                      case_pack: 5
                      unit_price: 4.31
                      unit_price_cents: 431
                      currency: USD
                      in_stock: true
                      quantity_available: 40
                      orderable: true
                      next_arrival_date: "2026-11-20"
                  not_found: [CL-OLD-SKU]
                  next_cursor: null
        "400": { $ref: "#/components/responses/Error400" }
        "401": { $ref: "#/components/responses/Error401" }
        "403": { $ref: "#/components/responses/Error403" }
        "429": { $ref: "#/components/responses/Error429" }
        "503": { $ref: "#/components/responses/Error503" }
  /api/v1/catalog:
    get:
      tags: [Catalog]
      operationId: syncCatalog
      summary: Sync the full catalog
      description: |
        Every item your website account can order, in the `/items` row shape plus `updated_at`, `on_order` and `backorderable`, oldest change first. Pages hold up to 200 rows. A page can hold fewer rows than `limit`, or none, and still have a `next_cursor`: keep paging until it is `null`. On the last page save `next_updated_since` and send it as `updated_since` next time.

        Your account can read 50,000 catalog rows a day (Pacific time), shared by all your keys. That is separate from the `/items` allowance.
      x-scope: catalog:read
      parameters:
        - $ref: "#/components/parameters/UpdatedSince"
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/Limit"
      responses:
        "200":
          description: One page of catalog rows.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success: { const: true }
                  data: { $ref: "#/components/schemas/CatalogPage" }
              example:
                success: true
                data:
                  items:
                    - sku: CL-CASE-IPH16-BK
                      upc: "012345678905"
                      name: Clear Case iPhone 16
                      color: Black
                      brand: CLICK
                      series: Clear Series
                      device: iPhone 16
                      image_url: https://www.balajiwireless.com/images/cl-case-iph16-bk.jpg
                      case_pack: 5
                      unit_price: 4.31
                      unit_price_cents: 431
                      currency: USD
                      in_stock: true
                      quantity_available: 40
                      orderable: true
                      next_arrival_date: "2026-11-20"
                      updated_at: "2026-11-05T00:00:00.000000Z"
                      on_order: 600
                      backorderable: false
                  next_cursor: null
                  next_updated_since: "2026-11-05T00:00:00.000000Z"
        "400": { $ref: "#/components/responses/Error400" }
        "401": { $ref: "#/components/responses/Error401" }
        "403": { $ref: "#/components/responses/Error403" }
        "429": { $ref: "#/components/responses/Error429" }
        "503": { $ref: "#/components/responses/Error503" }
  /api/v1/orders:
    get:
      tags: [Orders]
      operationId: listOrders
      summary: List orders
      description: |
        Sales orders from every channel (website, phone, your rep, EDI and this API) on your account and its sub-accounts, oldest change first. Changes reach this list up to 15 minutes after they happen.
      x-scope: orders:read
      parameters:
        - $ref: "#/components/parameters/UpdatedSince"
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/Limit"
        - name: status
          in: query
          description: Comma list of order statuses.
          schema:
            type: string
          example: pending_fulfillment,partially_fulfilled
        - name: po_number
          in: query
          description: Only orders with this PO number. 1 to 45 printable characters.
          schema: { type: string, minLength: 1, maxLength: 45 }
      responses:
        "200":
          description: One page of orders.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success: { const: true }
                  data: { $ref: "#/components/schemas/OrdersPage" }
              example:
                success: true
                data:
                  orders:
                    - order_number: SO51696890123
                      po_number: P26101
                      customer_id: 14442724
                      status: Pending Fulfillment
                      order_date: "2026-09-25"
                      total_cents: 12184
                      currency: USD
                      ship_method: UPS Ground
                      updated_at: "2026-09-25T17:02:11.123456Z"
                  next_cursor: null
                  next_updated_since: "2026-09-25T17:02:11.123456Z"
        "400": { $ref: "#/components/responses/Error400" }
        "401": { $ref: "#/components/responses/Error401" }
        "403": { $ref: "#/components/responses/Error403" }
        "429": { $ref: "#/components/responses/Error429" }
        "503": { $ref: "#/components/responses/Error503" }
    post:
      tags: [Orders]
      operationId: placeOrder
      summary: Place an order
      description: |
        Send one purchase order. It becomes one Balaji sales order on your account terms, placed through the same checkout the website uses, so every check a website order passes applies.

        - A **test key** runs every check and returns what the order would be (`"result": "validated"`). Nothing is placed.
        - **`?dry_run=true`** does the same on any key, including a live key, and saves nothing (`"result": "dry_run"`). Dry-run a full day's batch before your first live push.
        - A **live key** accepts the order and answers `202` with `"result": "pending_release"` and `release_after`. The order waits 30 minutes, then goes to the warehouse queue. Cancel it before then with `DELETE /api/v1/orders/{po_number}`. Live keys answer `503 LIVE_ORDERS_PAUSED` until Balaji switches live ordering on for your account.

        The call does not wait for checkout. Read the outcome with `GET /api/v1/orders/{po_number}`: it shows `pending_release`, then the order once it is placed, or `failed` with an `error_code` if checkout refused it (for example `CREDIT_LIMIT_EXCEEDED` or `OUT_OF_STOCK`).

        One PO number places one order. Send the same `po_number` again and nothing new is saved: you get the existing order back, with `"result": "already_placed"` once it is placed. That makes a retry after a timeout safe.

        If any line has a problem, nothing is accepted and the error lists every bad line.

        **Holds.** An order that looks wrong is accepted and held, not refused: it answers `202` with `"result": "held"` and a `holds` list, and nothing reaches checkout until a person releases it. A hold names the line and the number it was compared with. Holds: `LINE_QUANTITY_ABOVE_HISTORY` (a line over 10 times your largest line in the last 90 days), `ORDER_TOTAL_ABOVE_HISTORY` (a subtotal over 5 times your largest order in the last 90 days) and `CASE_PACK_ROUNDED` (a quantity that is not a whole number of case packs; checkout would round it up, so it is held instead). An item or customer with no 90-day history is not judged. A test key and a dry run show the holds too.

        **Daily ceilings.** Each key has a ceiling of orders, units and dollars a day, counted in Pacific time. An order that would pass one answers `429 DAILY_LIMIT_REACHED` and nothing is accepted. A dry run checks the ceiling and reserves nothing.
      x-scope: orders:write
      parameters:
        - name: dry_run
          in: query
          schema: { type: string, enum: ["true", "false"] }
          description: "`true` checks the order without placing or saving it."
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/OrderRequest" }
            example:
              po_number: P26101
              ship_to: { address_id: 4128 }
              lines:
                - { sku: ZZ-TFM-IP17-BK, quantity: 24, unit_price: 4.31, line_ref: "1" }
                - { upc: "812345678901", quantity: 12 }
              notes: Deliver to back door
      responses:
        "200":
          description: A test-key preview, a dry run, or an order that was already placed.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success: { const: true }
                  data: { $ref: "#/components/schemas/OrderResult" }
              example:
                success: true
                data:
                  mode: test
                  result: validated
                  placed: false
                  po_number: P26101
                  ship_to:
                    address_id: 4128
                    label: Store 17
                    addressee: Zadikuzz Group
                    addr1: 100 Calle Principal
                    addr2: null
                    city: San Juan
                    state: PR
                    zip: "00901"
                    country: US
                    store_id: null
                    default_shipping: false
                  store_id: null
                  shipping: { method: UPS Ground, estimated_rate: 18.4 }
                  lines:
                    - line: 0
                      line_ref: "1"
                      sku: ZZ-TFM-IP17-BK
                      requested_quantity: 24
                      quantity: 24
                      case_pack: 4
                      unit_price: 4.31
                      po_unit_price: 4.31
                      price_flag: false
                  subtotal: 103.44
                  subtotal_cents: 10344
                  currency: USD
                  price_flags: 0
                  holds: []
        "202":
          description: A live order was accepted and waits out its release window. A held order answers the same way with result held and its holds.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success: { const: true }
                  data: { $ref: "#/components/schemas/OrderResult" }
              example:
                success: true
                data:
                  mode: live
                  result: pending_release
                  placed: false
                  po_number: P26101
                  ship_to:
                    address_id: 4128
                    label: Store 17
                    addressee: Zadikuzz Group
                    addr1: 100 Calle Principal
                    addr2: null
                    city: San Juan
                    state: PR
                    zip: "00901"
                    country: US
                    store_id: null
                    default_shipping: false
                  store_id: null
                  shipping: { method: UPS Ground, estimated_rate: 18.4 }
                  lines:
                    - line: 0
                      line_ref: "1"
                      sku: ZZ-TFM-IP17-BK
                      requested_quantity: 24
                      quantity: 24
                      case_pack: 4
                      unit_price: 4.31
                      po_unit_price: 4.31
                      price_flag: false
                  subtotal: 103.44
                  subtotal_cents: 10344
                  currency: USD
                  price_flags: 0
                  holds: []
                  release_after: "2026-11-06T18:30:00.000Z"
        "400": { $ref: "#/components/responses/Error400" }
        "401": { $ref: "#/components/responses/Error401" }
        "403": { $ref: "#/components/responses/Error403" }
        "422": { $ref: "#/components/responses/Error422" }
        "429": { $ref: "#/components/responses/Error429" }
        "503": { $ref: "#/components/responses/Error503" }
  /api/v1/orders/{po_number}:
    get:
      tags: [Orders]
      operationId: getOrderByPo
      summary: Get an order by your PO number
      description: |
        Status, total and shipments of one order on your account or a sub-account, found by your own PO number. An order this API accepted shows here while it is held or waiting out its release window, before it is a sales order. A new shipment can take up to 15 minutes to appear. Check no more than once every 15 minutes per order; use `GET /api/v1/orders` to follow many orders.
      x-scope: orders:read
      parameters:
        - name: po_number
          in: path
          required: true
          schema: { type: string, minLength: 1, maxLength: 45 }
          example: P26101
      responses:
        "200":
          description: The order.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success: { const: true }
                  data: { $ref: "#/components/schemas/OrderStatus" }
              example:
                success: true
                data:
                  po_number: P26101
                  order_number: SO51696890123
                  status: Pending Fulfillment
                  total: 121.84
                  total_cents: 12184
                  currency: USD
                  order_date: 9/25/2026
                  shipments:
                    - shipment_number: IF1234567
                      ship_date: "2026-09-26"
                      tracking: [1Z999AA10123456784]
        "400": { $ref: "#/components/responses/Error400" }
        "401": { $ref: "#/components/responses/Error401" }
        "403": { $ref: "#/components/responses/Error403" }
        "404": { $ref: "#/components/responses/Error404" }
        "429": { $ref: "#/components/responses/Error429" }
        "503": { $ref: "#/components/responses/Error503" }
    delete:
      tags: [Orders]
      operationId: cancelOrder
      summary: Cancel an order
      description: |
        Cancels an order this API accepted while it is still held or waiting out its 30-minute release window. After that it has gone to the warehouse queue and cancellation goes through customer service (`CANCEL_WINDOW_CLOSED`). Cancelling twice answers cancelled again. An order checkout refused was never placed (`ORDER_NOT_PLACED`). It gives back the order's daily ceiling slot.
      x-scope: orders:write
      parameters:
        - name: po_number
          in: path
          required: true
          schema: { type: string, minLength: 1, maxLength: 45 }
          example: P26101
      responses:
        "200":
          description: The order is cancelled.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success: { const: true }
                  data:
                    type: object
                    required: [po_number, result, status]
                    properties:
                      po_number: { type: string }
                      result: { const: cancelled }
                      status: { const: cancelled }
              example:
                success: true
                data: { po_number: P26101, result: cancelled, status: cancelled }
        "400": { $ref: "#/components/responses/Error400" }
        "401": { $ref: "#/components/responses/Error401" }
        "403": { $ref: "#/components/responses/Error403" }
        "404": { $ref: "#/components/responses/Error404" }
        "409": { $ref: "#/components/responses/Error409" }
        "429": { $ref: "#/components/responses/Error429" }
        "503": { $ref: "#/components/responses/Error503" }
  /api/v1/orders/by-number/{order_number}:
    get:
      tags: [Orders]
      operationId: getOrderByNumber
      summary: Get an order by Balaji order number
      description: One order on your account or a sub-account by its Balaji order number, with its shipments. Use it for orders placed without a PO number.
      x-scope: orders:read
      parameters:
        - name: order_number
          in: path
          required: true
          schema: { type: string, pattern: "^[A-Za-z0-9-]{1,40}$" }
          example: SO51696890123
      responses:
        "200":
          description: The order with its shipments.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success: { const: true }
                  data: { $ref: "#/components/schemas/OrderWithShipments" }
              example:
                success: true
                data:
                  order_number: SO51696890123
                  po_number: P26101
                  customer_id: 14442724
                  status: Pending Fulfillment
                  order_date: "2026-09-25"
                  total_cents: 12184
                  currency: USD
                  ship_method: UPS Ground
                  updated_at: "2026-09-25T17:02:11.123456Z"
                  shipments:
                    - shipment_number: IF1234567
                      order_number: SO51696890123
                      po_number: P26101
                      customer_id: 14442724
                      status: Shipped
                      ship_date: "2026-09-26"
                      ship_method: UPS Ground
                      tracking:
                        - number: 1Z999AA10123456784
                          carrier: ups
                          url: https://www.ups.com/track?tracknum=1Z999AA10123456784
                      carrier_status: In Transit
                      updated_at: "2026-09-26T21:40:03.000000Z"
        "400": { $ref: "#/components/responses/Error400" }
        "401": { $ref: "#/components/responses/Error401" }
        "403": { $ref: "#/components/responses/Error403" }
        "404": { $ref: "#/components/responses/Error404" }
        "429": { $ref: "#/components/responses/Error429" }
        "503": { $ref: "#/components/responses/Error503" }
  /api/v1/shipments:
    get:
      tags: [Shipments]
      operationId: listShipments
      summary: List shipments
      description: |
        Fulfillments on your account and its sub-accounts with order number, PO, ship method, tracking numbers and links, and the last UPS status we recorded. `carrier` and `url` are `null` when the carrier cannot be told from the number. Changes reach this list up to 15 minutes after they happen.
      x-scope: shipments:read
      parameters:
        - $ref: "#/components/parameters/UpdatedSince"
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/Limit"
      responses:
        "200":
          description: One page of shipments.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success: { const: true }
                  data: { $ref: "#/components/schemas/ShipmentsPage" }
              example:
                success: true
                data:
                  shipments:
                    - shipment_number: IF1234567
                      order_number: SO51696890123
                      po_number: P26101
                      customer_id: 14442724
                      status: Shipped
                      ship_date: "2026-09-26"
                      ship_method: UPS Ground
                      tracking:
                        - number: 1Z999AA10123456784
                          carrier: ups
                          url: https://www.ups.com/track?tracknum=1Z999AA10123456784
                      carrier_status: In Transit
                      updated_at: "2026-09-26T21:40:03.000000Z"
                  next_cursor: null
                  next_updated_since: "2026-09-26T21:40:03.000000Z"
        "400": { $ref: "#/components/responses/Error400" }
        "401": { $ref: "#/components/responses/Error401" }
        "403": { $ref: "#/components/responses/Error403" }
        "429": { $ref: "#/components/responses/Error429" }
        "503": { $ref: "#/components/responses/Error503" }
  /api/v1/invoices:
    get:
      tags: [Finance]
      operationId: listInvoices
      summary: List invoices
      description: Invoices on your account and its sub-accounts, oldest change first. `open=true` keeps invoices with an amount due. Each row links its PDF at `pdf_url`.
      x-scope: finance:read
      parameters:
        - $ref: "#/components/parameters/UpdatedSince"
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/Limit"
        - name: open
          in: query
          schema: { type: string, enum: ["true", "false"] }
          description: "`true` keeps invoices with an amount due."
      responses:
        "200":
          description: One page of invoices.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success: { const: true }
                  data: { $ref: "#/components/schemas/InvoicesPage" }
              example:
                success: true
                data:
                  invoices:
                    - id: 8800123
                      invoice_number: INV90412
                      customer_id: 14442724
                      status: Open
                      issued_on: "2026-09-26"
                      due_on: "2026-10-26"
                      overdue: false
                      terms: Net 30
                      po_number: P26101
                      total_cents: 12184
                      amount_due_cents: 12184
                      currency: USD
                      pdf_url: /api/v1/invoices/8800123/pdf
                      updated_at: "2026-09-26T22:10:00.000000Z"
                  next_cursor: null
                  next_updated_since: "2026-09-26T22:10:00.000000Z"
        "400": { $ref: "#/components/responses/Error400" }
        "401": { $ref: "#/components/responses/Error401" }
        "403": { $ref: "#/components/responses/Error403" }
        "429": { $ref: "#/components/responses/Error429" }
        "503": { $ref: "#/components/responses/Error503" }
  /api/v1/invoices/{invoice_id}:
    get:
      tags: [Finance]
      operationId: getInvoice
      summary: Get an invoice
      description: One invoice with its lines. Another account's invoice and a missing one both answer 404.
      x-scope: finance:read
      parameters:
        - name: invoice_id
          in: path
          required: true
          schema: { type: integer, minimum: 1 }
          example: 8800123
      responses:
        "200":
          description: The invoice with its lines.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success: { const: true }
                  data: { $ref: "#/components/schemas/InvoiceDetail" }
              example:
                success: true
                data:
                  id: 8800123
                  invoice_number: INV90412
                  customer_id: 14442724
                  status: Open
                  issued_on: "2026-09-26"
                  due_on: "2026-10-26"
                  overdue: false
                  terms: Net 30
                  po_number: P26101
                  total_cents: 12184
                  amount_due_cents: 12184
                  currency: USD
                  pdf_url: /api/v1/invoices/8800123/pdf
                  updated_at: "2026-09-26T22:10:00.000000Z"
                  lines:
                    - item_id: 51234
                      sku: ZZ-TFM-IP17-BK
                      name: Tempered Glass iPhone 17 Black
                      item_type: InvtPart
                      quantity: 24
                      rate_cents: 431
                      amount_cents: 10344
                      currency: USD
        "400": { $ref: "#/components/responses/Error400" }
        "401": { $ref: "#/components/responses/Error401" }
        "403": { $ref: "#/components/responses/Error403" }
        "404": { $ref: "#/components/responses/Error404" }
        "429": { $ref: "#/components/responses/Error429" }
        "503": { $ref: "#/components/responses/Error503" }
  /api/v1/invoices/{invoice_id}/pdf:
    get:
      tags: [Finance]
      operationId: getInvoicePdf
      summary: Download an invoice PDF
      description: The invoice as the branded Balaji PDF. Another account's invoice and a missing one both answer 404.
      x-scope: finance:read
      parameters:
        - name: invoice_id
          in: path
          required: true
          schema: { type: integer, minimum: 1 }
          example: 8800123
      responses:
        "200":
          description: The PDF, sent as an attachment.
          content:
            application/pdf:
              schema: { type: string, contentMediaType: application/pdf }
        "400": { $ref: "#/components/responses/Error400" }
        "401": { $ref: "#/components/responses/Error401" }
        "403": { $ref: "#/components/responses/Error403" }
        "404": { $ref: "#/components/responses/Error404" }
        "429": { $ref: "#/components/responses/Error429" }
        "503": { $ref: "#/components/responses/Error503" }
  /api/v1/credit-memos:
    get:
      tags: [Finance]
      operationId: listCreditMemos
      summary: List credit memos
      description: Credit memos on your account and its sub-accounts, with the invoices each was applied to and what is still unused.
      x-scope: finance:read
      parameters:
        - $ref: "#/components/parameters/UpdatedSince"
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/Limit"
      responses:
        "200":
          description: One page of credit memos.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success: { const: true }
                  data: { $ref: "#/components/schemas/CreditMemosPage" }
              example:
                success: true
                data:
                  credit_memos:
                    - id: 8800456
                      number: CM2201
                      customer_id: 14442724
                      status: Open
                      issued_on: "2026-10-02"
                      total_cents: 2500
                      amount_remaining_cents: 2500
                      currency: USD
                      applied_to: []
                      updated_at: "2026-10-02T18:00:00.000000Z"
                  next_cursor: null
                  next_updated_since: "2026-10-02T18:00:00.000000Z"
        "400": { $ref: "#/components/responses/Error400" }
        "401": { $ref: "#/components/responses/Error401" }
        "403": { $ref: "#/components/responses/Error403" }
        "429": { $ref: "#/components/responses/Error429" }
        "503": { $ref: "#/components/responses/Error503" }
  /api/v1/payments:
    get:
      tags: [Finance]
      operationId: listPayments
      summary: List payments
      description: Customer payments on your account and its sub-accounts, with the invoices each was applied to and any amount not yet applied.
      x-scope: finance:read
      parameters:
        - $ref: "#/components/parameters/UpdatedSince"
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/Limit"
      responses:
        "200":
          description: One page of payments.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success: { const: true }
                  data: { $ref: "#/components/schemas/PaymentsPage" }
              example:
                success: true
                data:
                  payments:
                    - id: 8800789
                      number: PYMT3301
                      customer_id: 14442724
                      status: Deposited
                      issued_on: "2026-10-20"
                      total_cents: 12184
                      amount_remaining_cents: 0
                      currency: USD
                      applied_to:
                        - { id: 8800123, invoice_number: INV90412 }
                      updated_at: "2026-10-20T16:30:00.000000Z"
                  next_cursor: null
                  next_updated_since: "2026-10-20T16:30:00.000000Z"
        "400": { $ref: "#/components/responses/Error400" }
        "401": { $ref: "#/components/responses/Error401" }
        "403": { $ref: "#/components/responses/Error403" }
        "429": { $ref: "#/components/responses/Error429" }
        "503": { $ref: "#/components/responses/Error503" }
  /api/v1/returns:
    get:
      tags: [Finance]
      operationId: listReturns
      summary: List returns
      description: Return authorizations (RMAs) on your account and its sub-accounts, with the order each came from and the credit memo it produced, when there is one.
      x-scope: finance:read
      parameters:
        - $ref: "#/components/parameters/UpdatedSince"
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/Limit"
      responses:
        "200":
          description: One page of returns.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success: { const: true }
                  data: { $ref: "#/components/schemas/ReturnsPage" }
              example:
                success: true
                data:
                  returns:
                    - id: 8800321
                      number: RMA1105
                      customer_id: 14442724
                      status: Refunded
                      requested_on: "2026-10-01"
                      total_cents: 2500
                      currency: USD
                      source_order: { id: 7700123, order_number: SO51696890123 }
                      credit_memo: { id: 8800456, number: CM2201, total_cents: 2500, currency: USD }
                      updated_at: "2026-10-02T18:00:00.000000Z"
                  next_cursor: null
                  next_updated_since: "2026-10-02T18:00:00.000000Z"
        "400": { $ref: "#/components/responses/Error400" }
        "401": { $ref: "#/components/responses/Error401" }
        "403": { $ref: "#/components/responses/Error403" }
        "429": { $ref: "#/components/responses/Error429" }
        "503": { $ref: "#/components/responses/Error503" }
  /api/v1/statement:
    get:
      tags: [Finance]
      operationId: getStatement
      summary: Get your statement
      description: |
        Your open balance, aging buckets and open items: open invoices, then unused credit memos and payments as negative amounts, so the open items sum to the balance. It is the current state only. There is no `as_of` parameter; the `as_of` field reports how fresh the data is.
      x-scope: finance:read
      responses:
        "200":
          description: The statement.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success: { const: true }
                  data: { $ref: "#/components/schemas/Statement" }
              example:
                success: true
                data:
                  as_of: "2026-11-05T17:45:00.000Z"
                  balance_cents: 9684
                  currency: USD
                  aging:
                    current: { count: 1, amount_cents: 12184 }
                    days_1_30: { count: 0, amount_cents: 0 }
                    days_31_60: { count: 0, amount_cents: 0 }
                    days_61_90: { count: 0, amount_cents: 0 }
                    days_91_plus: { count: 0, amount_cents: 0 }
                  open_items:
                    - kind: invoice
                      id: 8800123
                      number: INV90412
                      issued_on: "2026-09-26"
                      due_on: "2026-10-26"
                      days_past_due: null
                      amount_cents: 12184
                      currency: USD
                    - kind: credit_memo
                      id: 8800456
                      number: CM2201
                      issued_on: "2026-10-02"
                      due_on: null
                      days_past_due: null
                      amount_cents: -2500
                      currency: USD
        "401": { $ref: "#/components/responses/Error401" }
        "403": { $ref: "#/components/responses/Error403" }
        "429": { $ref: "#/components/responses/Error429" }
        "503": { $ref: "#/components/responses/Error503" }
  /api/v1/ship-tos:
    get:
      tags: [Ship-tos]
      operationId: listShipTos
      summary: List ship-to addresses
      description: Your own ship-to addresses. Map your store codes to `address_id` once with `PUT /api/v1/ship-to-map`.
      x-scope: orders:read
      responses:
        "200":
          description: Your ship-to addresses.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success: { const: true }
                  data:
                    type: object
                    required: [ship_tos]
                    properties:
                      ship_tos:
                        type: array
                        items: { $ref: "#/components/schemas/ShipTo" }
              example:
                success: true
                data:
                  ship_tos:
                    - address_id: 4128
                      label: Store 17
                      addressee: Zadikuzz Group
                      addr1: 100 Calle Principal
                      addr2: null
                      city: San Juan
                      state: PR
                      zip: "00901"
                      country: US
                      store_id: null
                      default_shipping: false
        "401": { $ref: "#/components/responses/Error401" }
        "403": { $ref: "#/components/responses/Error403" }
        "429": { $ref: "#/components/responses/Error429" }
        "503": { $ref: "#/components/responses/Error503" }
  /api/v1/ship-to-map:
    get:
      tags: [Ship-tos]
      operationId: getShipToMap
      summary: Get your store map
      description: Your store codes and the ship-to address each maps to.
      x-scope: orders:read
      responses:
        "200":
          description: Your store map.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success: { const: true }
                  data: { $ref: "#/components/schemas/StoreMap" }
              example:
                success: true
                data:
                  stores:
                    - { store_id: S17, address_id: 4128, updated_at: "2026-11-05T18:00:00.000Z" }
        "401": { $ref: "#/components/responses/Error401" }
        "403": { $ref: "#/components/responses/Error403" }
        "429": { $ref: "#/components/responses/Error429" }
        "503": { $ref: "#/components/responses/Error503" }
    put:
      tags: [Ship-tos]
      operationId: putShipToMap
      summary: Replace your store map
      description: |
        Replaces your whole store map. Store codes are not case sensitive. If any `address_id` is not one of your addresses, nothing is saved. Once you have a map, every `POST /api/v1/orders` must name `ship_to.store_id` and ships to the address that store maps to.
      x-scope: orders:write
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/StoreMapRequest" }
            example:
              stores:
                - { store_id: S17, address_id: 4128 }
                - { store_id: S18, address_id: 4129 }
      responses:
        "200":
          description: The map as saved.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success: { const: true }
                  data: { $ref: "#/components/schemas/StoreMap" }
              example:
                success: true
                data:
                  stores:
                    - { store_id: S17, address_id: 4128, updated_at: "2026-11-05T18:00:00.000Z" }
                    - { store_id: S18, address_id: 4129, updated_at: "2026-11-05T18:00:00.000Z" }
        "400": { $ref: "#/components/responses/Error400" }
        "401": { $ref: "#/components/responses/Error401" }
        "403": { $ref: "#/components/responses/Error403" }
        "422": { $ref: "#/components/responses/Error422" }
        "429": { $ref: "#/components/responses/Error429" }
        "503": { $ref: "#/components/responses/Error503" }
  /api/v1/webhooks:
    get:
      tags: [Webhooks]
      operationId: listWebhooks
      summary: List webhook endpoints
      description: Your account's webhook endpoints, newest first. Sending is switched off until Balaji turns it on for your account; registration works before that.
      x-scope: webhooks:manage
      responses:
        "200":
          description: Your endpoints.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success: { const: true }
                  data:
                    type: object
                    required: [endpoints]
                    properties:
                      endpoints:
                        type: array
                        items: { $ref: "#/components/schemas/WebhookEndpoint" }
              example:
                success: true
                data:
                  endpoints:
                    - id: cwe_8f2c1a9b0d3e4f56
                      url: https://erp.example.com/balaji/hooks
                      description: ERP order sync
                      events: [order.created, order.updated, shipment.created]
                      mode: test
                      secret_prefix: whsec_3kq9
                      status: active
                      disabled_reason: null
                      disabled_at: null
                      failing_since: null
                      last_success_at: "2026-11-06T09:12:44.000Z"
                      created_at: "2026-11-05T18:00:00.000Z"
        "401": { $ref: "#/components/responses/Error401" }
        "403": { $ref: "#/components/responses/Error403" }
        "429": { $ref: "#/components/responses/Error429" }
        "503": { $ref: "#/components/responses/Error503" }
    post:
      tags: [Webhooks]
      operationId: createWebhook
      summary: Add a webhook endpoint
      description: |
        Registers an https endpoint for the events you list. The answer carries the signing secret (`whsec_...`) once; store it, because it is never shown again. A test key makes a test endpoint and a live key a live one. Up to 10 endpoints per account: a further one answers 409 `TOO_MANY_ENDPOINTS`, and a URL that is not https or not public answers 422 `INVALID_URL`.

        Each delivery is a POST with the header `Balaji-Signature: t=<unix seconds>,v1=<hex>`, where `v1` is HMAC-SHA256 of `<t>.<raw body>` with your secret. Reject a signature older than 5 minutes.
      x-scope: webhooks:manage
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [url, events]
              properties:
                url: { type: string, maxLength: 2048 }
                events:
                  type: array
                  minItems: 1
                  items: { $ref: "#/components/schemas/WebhookEvent" }
                description: { type: string, maxLength: 200 }
            example:
              url: https://erp.example.com/balaji/hooks
              events: [order.created, order.updated, shipment.created]
              description: ERP order sync
      responses:
        "201":
          description: The endpoint, with its signing secret shown this once.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success: { const: true }
                  data:
                    type: object
                    required: [endpoint, secret]
                    properties:
                      endpoint: { $ref: "#/components/schemas/WebhookEndpoint" }
                      secret: { type: string }
              example:
                success: true
                data:
                  endpoint:
                    id: cwe_8f2c1a9b0d3e4f56
                    url: https://erp.example.com/balaji/hooks
                    description: ERP order sync
                    events: [order.created, order.updated, shipment.created]
                    mode: test
                    secret_prefix: whsec_3kq9
                    status: active
                    disabled_reason: null
                    disabled_at: null
                    failing_since: null
                    last_success_at: null
                    created_at: "2026-11-05T18:00:00.000Z"
                  secret: whsec_3kq9EXAMPLEONLYnotarealsecret0000000000
        "400": { $ref: "#/components/responses/Error400" }
        "401": { $ref: "#/components/responses/Error401" }
        "403": { $ref: "#/components/responses/Error403" }
        "409": { $ref: "#/components/responses/Error409" }
        "422": { $ref: "#/components/responses/Error422" }
        "429": { $ref: "#/components/responses/Error429" }
        "503": { $ref: "#/components/responses/Error503" }
  /api/v1/webhooks/{endpoint_id}:
    get:
      tags: [Webhooks]
      operationId: getWebhook
      summary: Get a webhook endpoint
      description: One endpoint. Another account's endpoint and a missing one both answer 404.
      x-scope: webhooks:manage
      parameters:
        - $ref: "#/components/parameters/WebhookEndpointId"
      responses:
        "200":
          description: The endpoint.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success: { const: true }
                  data:
                    type: object
                    required: [endpoint]
                    properties:
                      endpoint: { $ref: "#/components/schemas/WebhookEndpoint" }
              example:
                success: true
                data:
                  endpoint:
                    id: cwe_8f2c1a9b0d3e4f56
                    url: https://erp.example.com/balaji/hooks
                    description: ERP order sync
                    events: [order.created]
                    mode: live
                    secret_prefix: whsec_3kq9
                    status: disabled
                    disabled_reason: Deliveries failed for 24 hours.
                    disabled_at: "2026-11-06T09:00:00.000Z"
                    failing_since: "2026-11-05T09:00:00.000Z"
                    last_success_at: "2026-11-05T08:58:10.000Z"
                    created_at: "2026-11-01T18:00:00.000Z"
        "401": { $ref: "#/components/responses/Error401" }
        "403": { $ref: "#/components/responses/Error403" }
        "404": { $ref: "#/components/responses/Error404" }
        "429": { $ref: "#/components/responses/Error429" }
        "503": { $ref: "#/components/responses/Error503" }
    patch:
      tags: [Webhooks]
      operationId: updateWebhook
      summary: Change a webhook endpoint
      description: Send any of `url`, `events`, `description`, or `enabled true` to turn a disabled endpoint back on. A bad URL answers 422 `INVALID_URL`.
      x-scope: webhooks:manage
      parameters:
        - $ref: "#/components/parameters/WebhookEndpointId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              minProperties: 1
              properties:
                url: { type: string, maxLength: 2048 }
                events:
                  type: array
                  minItems: 1
                  items: { $ref: "#/components/schemas/WebhookEvent" }
                description: { type: [string, "null"], maxLength: 200 }
                enabled: { const: true }
            example:
              enabled: true
      responses:
        "200":
          description: The endpoint as saved.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success: { const: true }
                  data:
                    type: object
                    required: [endpoint]
                    properties:
                      endpoint: { $ref: "#/components/schemas/WebhookEndpoint" }
              example:
                success: true
                data:
                  endpoint:
                    id: cwe_8f2c1a9b0d3e4f56
                    url: https://erp.example.com/balaji/hooks
                    description: ERP order sync
                    events: [order.created]
                    mode: live
                    secret_prefix: whsec_3kq9
                    status: active
                    disabled_reason: null
                    disabled_at: null
                    failing_since: null
                    last_success_at: "2026-11-05T08:58:10.000Z"
                    created_at: "2026-11-01T18:00:00.000Z"
        "400": { $ref: "#/components/responses/Error400" }
        "401": { $ref: "#/components/responses/Error401" }
        "403": { $ref: "#/components/responses/Error403" }
        "404": { $ref: "#/components/responses/Error404" }
        "422": { $ref: "#/components/responses/Error422" }
        "429": { $ref: "#/components/responses/Error429" }
        "503": { $ref: "#/components/responses/Error503" }
    delete:
      tags: [Webhooks]
      operationId: deleteWebhook
      summary: Remove a webhook endpoint
      description: Removes the endpoint and drops its unsent events.
      x-scope: webhooks:manage
      parameters:
        - $ref: "#/components/parameters/WebhookEndpointId"
      responses:
        "200":
          description: Removed.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success: { const: true }
                  data:
                    type: object
                    required: [deleted]
                    properties:
                      deleted: { const: true }
              example: { success: true, data: { deleted: true } }
        "401": { $ref: "#/components/responses/Error401" }
        "403": { $ref: "#/components/responses/Error403" }
        "404": { $ref: "#/components/responses/Error404" }
        "429": { $ref: "#/components/responses/Error429" }
        "503": { $ref: "#/components/responses/Error503" }
  /api/v1/webhooks/{endpoint_id}/test:
    post:
      tags: [Webhooks]
      operationId: testWebhook
      summary: Send a test event
      description: Sends one sample event of the type you name, marked `test true`, once, and answers with what your endpoint returned. A failed test never disables the endpoint. 503 `WEBHOOKS_PAUSED` while sending is switched off; 409 `ENDPOINT_DISABLED` for a disabled endpoint.
      x-scope: webhooks:manage
      parameters:
        - $ref: "#/components/parameters/WebhookEndpointId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [type]
              properties:
                type: { $ref: "#/components/schemas/WebhookEvent" }
            example: { type: order.created }
      responses:
        "200":
          description: The attempt.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success: { const: true }
                  data:
                    type: object
                    required: [event_id, delivered, status_code, error]
                    properties:
                      event_id: { type: string }
                      delivered: { type: boolean }
                      status_code: { type: [integer, "null"] }
                      error: { type: [string, "null"] }
              example:
                success: true
                data: { event_id: evt_5b1d2c3e4f5a6b7c, delivered: true, status_code: 204, error: null }
        "400": { $ref: "#/components/responses/Error400" }
        "401": { $ref: "#/components/responses/Error401" }
        "403": { $ref: "#/components/responses/Error403" }
        "404": { $ref: "#/components/responses/Error404" }
        "409": { $ref: "#/components/responses/Error409" }
        "429": { $ref: "#/components/responses/Error429" }
        "503": { $ref: "#/components/responses/Error503" }
  /api/v1/webhooks/{endpoint_id}/deliveries:
    get:
      tags: [Webhooks]
      operationId: listWebhookDeliveries
      summary: List delivery attempts
      description: Recent attempts for one endpoint, newest first.
      x-scope: webhooks:manage
      parameters:
        - $ref: "#/components/parameters/WebhookEndpointId"
        - name: limit
          in: query
          description: Attempts to return, 1 to 200. Default 50.
          schema: { type: integer, minimum: 1, maximum: 200, default: 50 }
      responses:
        "200":
          description: The attempts.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success: { const: true }
                  data:
                    type: object
                    required: [deliveries]
                    properties:
                      deliveries:
                        type: array
                        items: { $ref: "#/components/schemas/WebhookDelivery" }
              example:
                success: true
                data:
                  deliveries:
                    - id: 9120
                      event_id: evt_5b1d2c3e4f5a6b7c
                      event_type: order.created
                      test: true
                      attempt: 1
                      status_code: 204
                      ok: true
                      error: null
                      duration_ms: 182
                      created_at: "2026-11-06T09:12:44.000Z"
        "400": { $ref: "#/components/responses/Error400" }
        "401": { $ref: "#/components/responses/Error401" }
        "403": { $ref: "#/components/responses/Error403" }
        "404": { $ref: "#/components/responses/Error404" }
        "429": { $ref: "#/components/responses/Error429" }
        "503": { $ref: "#/components/responses/Error503" }
  /api/v1/webhooks/{endpoint_id}/rotate-secret:
    post:
      tags: [Webhooks]
      operationId: rotateWebhookSecret
      summary: Replace the signing secret
      description: Mints a new signing secret and shows it once. The old secret stops working at once.
      x-scope: webhooks:manage
      parameters:
        - $ref: "#/components/parameters/WebhookEndpointId"
      responses:
        "200":
          description: The endpoint and its new secret.
          content:
            application/json:
              schema:
                type: object
                required: [success, data]
                properties:
                  success: { const: true }
                  data:
                    type: object
                    required: [endpoint, secret]
                    properties:
                      endpoint: { $ref: "#/components/schemas/WebhookEndpoint" }
                      secret: { type: string }
              example:
                success: true
                data:
                  endpoint:
                    id: cwe_8f2c1a9b0d3e4f56
                    url: https://erp.example.com/balaji/hooks
                    description: null
                    events: [order.created]
                    mode: test
                    secret_prefix: whsec_7mz2
                    status: active
                    disabled_reason: null
                    disabled_at: null
                    failing_since: null
                    last_success_at: null
                    created_at: "2026-11-05T18:00:00.000Z"
                  secret: whsec_7mz2EXAMPLEONLYnotarealsecret0000000000
        "401": { $ref: "#/components/responses/Error401" }
        "403": { $ref: "#/components/responses/Error403" }
        "404": { $ref: "#/components/responses/Error404" }
        "429": { $ref: "#/components/responses/Error429" }
        "503": { $ref: "#/components/responses/Error503" }
  /api/v1/oauth/token:
    post:
      tags: [Partner OAuth]
      operationId: partnerToken
      summary: Exchange a code or refresh a token
      description: |
        For partner platforms only. Authenticate the client with its id and secret (HTTP Basic, or `client_id` and `client_secret` form fields). Grants: `authorization_code` and `refresh_token`. A code or refresh token presented twice revokes its connection. See the partner guide in the docs.
      security: []
      x-scope: none
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              required: [grant_type]
              properties:
                grant_type: { type: string, enum: [authorization_code, refresh_token] }
                code: { type: string }
                redirect_uri: { type: string }
                code_verifier: { type: string }
                refresh_token: { type: string }
                client_id: { type: string }
                client_secret: { type: string }
      responses:
        "200":
          description: A token pair.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/OAuthToken" }
              example:
                access_token: bbw_pat_example
                token_type: Bearer
                expires_in: 3600
                refresh_token: bbw_prt_example
                scope: order_api
        "400":
          description: The request or grant is wrong.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/OAuthError" }
              example: { error: invalid_grant }
        "401":
          description: The client id or secret is wrong.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/OAuthError" }
              example: { error: invalid_client }
        "403":
          description: The shop's account no longer has online access.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/OAuthError" }
              example: { error: ACCOUNT_INACTIVE, error_description: This account no longer has online access. }
        "503":
          description: Temporarily unavailable.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/OAuthError" }
              example: { error: temporarily_unavailable }
  /api/v1/oauth/revoke:
    post:
      tags: [Partner OAuth]
      operationId: partnerRevoke
      summary: Revoke a connection
      description: |
        For partner platforms only. RFC 7009 revocation. Either token of a connection revokes the whole connection. It answers 200 for an unknown token, so the call cannot probe for valid tokens.
      security: []
      x-scope: none
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              required: [token]
              properties:
                token: { type: string }
                client_id: { type: string }
                client_secret: { type: string }
      responses:
        "200": { description: Revoked, or the token was unknown. No body. }
        "400":
          description: The request is wrong.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/OAuthError" }
              example: { error: invalid_request, error_description: Missing token. }
        "401":
          description: The client id or secret is wrong.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/OAuthError" }
              example: { error: invalid_client }
        "503":
          description: Temporarily unavailable.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/OAuthError" }
              example: { error: temporarily_unavailable }
components:
  securitySchemes:
    bearerKey:
      type: http
      scheme: bearer
      description: |
        A key from your Integrations page (`bbw_test_...` or `bbw_live_...`), or the access token of a partner connection (`bbw_pat_...`). Send it as `Authorization: Bearer <key>`.
  parameters:
    UpdatedSince:
      name: updated_since
      in: query
      description: Only rows changed after this time. ISO 8601 with a zone. We reach back 30 minutes before it, so you may see a row again.
      schema: { type: string, format: date-time }
      example: "2026-11-05T08:00:00Z"
    Cursor:
      name: cursor
      in: query
      description: The `next_cursor` of the previous page. Send the same filters with it.
      schema: { type: string }
    Limit:
      name: limit
      in: query
      description: Rows per page, 1 to 200. Default 100.
      schema: { type: integer, minimum: 1, maximum: 200, default: 100 }
    WebhookEndpointId:
      name: endpoint_id
      in: path
      required: true
      schema: { type: string }
      example: cwe_8f2c1a9b0d3e4f56
  responses:
    Error400:
      description: The request is wrong. Read `message`.
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
          example: { success: false, error: { code: VALIDATION_ERROR, message: "limit must be a whole number from 1 to 200." } }
    Error401:
      description: The key is missing, wrong, expired or revoked.
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
          example: { success: false, error: { code: UNAUTHORIZED, message: "Missing, invalid, expired or revoked credential." } }
    Error403:
      description: The key lacks the scope, or the account cannot do this now.
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
          example: { success: false, error: { code: INSUFFICIENT_SCOPE, message: "This key does not have the finance:read scope.", required_scope: "finance:read" } }
    Error404:
      description: No such record on your account.
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
          example: { success: false, error: { code: NOT_FOUND, message: "No order with that po_number on your account." } }
    Error409:
      description: The order cannot be cancelled.
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
          example: { success: false, error: { code: CANCEL_WINDOW_CLOSED, message: "This order has gone to our warehouse queue, so it can no longer be cancelled here. Contact Balaji customer service to cancel it.", status: Pending Fulfillment, order_number: SO51696890123 } }
    Error422:
      description: The request is well formed but cannot be accepted. `INVALID_LINES` lists every bad line in `problems`.
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
          example:
            success: false
            error:
              code: INVALID_LINES
              message: 1 line(s) cannot be ordered.
              problems:
                - { line: 1, line_ref: "2", code: UNKNOWN_ITEM, message: "UPC 812345678901 is not on an active item." }
    Error429:
      description: Too many requests, the daily item allowance is used, or an order would pass a daily ceiling. `Retry-After` gives the seconds to wait on `RATE_LIMITED` and `DAILY_LIMIT_REACHED`.
      headers:
        Retry-After:
          schema: { type: integer }
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
          example: { success: false, error: { code: RATE_LIMITED, message: "Too many requests. Retry after the Retry-After seconds." } }
    Error503:
      description: Temporarily unavailable. Retry the same request.
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
          example: { success: false, error: { code: UPSTREAM_UNAVAILABLE, message: "We could not read your orders. Retry shortly." } }
  schemas:
    Error:
      type: object
      required: [success, error]
      properties:
        success: { const: false }
        error:
          type: object
          required: [code, message]
          properties:
            code:
              type: string
              description: See the error code table in the docs.
            message: { type: string }
            required_scope: { type: string }
            error_code: { type: [string, "null"] }
            status: { type: string }
            order_number: { type: [string, "null"] }
            problems:
              type: array
              items: { $ref: "#/components/schemas/LineProblem" }
            ceiling: { type: string, enum: [orders, units, cents], description: Which daily ceiling an order would pass. }
            used: { type: object }
            limits: { type: object }
            currency: { type: string }
            store_ids:
              type: array
              items: { type: string }
            stores:
              type: array
              items: { type: object }
            upstream: { type: object }
          additionalProperties: true
    LineProblem:
      type: object
      required: [line, line_ref, code, message]
      properties:
        line: { type: integer, minimum: 0, description: Zero-based position of the line in your request. }
        line_ref: { type: [string, "null"] }
        code: { type: string, enum: [UNKNOWN_ITEM, AMBIGUOUS_UPC, DUPLICATE_ITEM, NO_PRICE] }
        message: { type: string }
    PageMeta:
      type: object
      required: [next_cursor, next_updated_since]
      properties:
        next_cursor:
          type: [string, "null"]
          description: Send it back as `cursor` for the next page. `null` on the last page.
        next_updated_since:
          type: [string, "null"]
          description: On the last page, the newest change returned. Send it as `updated_since` next time.
    Item:
      type: object
      required: [sku, upc, name, color, brand, series, device, image_url, case_pack, unit_price, unit_price_cents, currency, in_stock, quantity_available, orderable, next_arrival_date]
      properties:
        sku: { type: string }
        upc: { type: [string, "null"] }
        name: { type: string }
        color: { type: [string, "null"] }
        brand: { type: [string, "null"] }
        series: { type: [string, "null"] }
        device: { type: [string, "null"] }
        image_url: { type: [string, "null"] }
        case_pack: { type: integer, minimum: 1, description: Order in multiples of it. }
        unit_price: { type: [number, "null"], description: Your price for one unit in dollars. Prefer `unit_price_cents`. `null` while your account is not approved for wholesale prices. }
        unit_price_cents: { type: [integer, "null"], description: Your price for one unit, in whole cents of `currency`. }
        currency: { type: string, description: ISO 4217 code. }
        in_stock: { type: boolean }
        quantity_available: { type: integer, minimum: 0, description: The exact count the website shows you. }
        orderable: { type: boolean, description: "`false` when there is no price for you or the item needs approval first." }
        next_arrival_date: { type: [string, "null"], format: date, description: The expected restock date shown on the website. It may move. `null` when there is none. }
    ItemsResult:
      type: object
      required: [items, not_found, next_cursor]
      properties:
        items:
          type: array
          items: { $ref: "#/components/schemas/Item" }
        not_found:
          type: array
          items: { type: string }
          description: Each `sku` or `upc` you sent that is not an active item you can order.
        next_cursor: { type: [string, "null"] }
    CatalogItem:
      allOf:
        - $ref: "#/components/schemas/Item"
        - type: object
          required: [updated_at, on_order, backorderable]
          properties:
            updated_at: { type: string, format: date-time }
            on_order: { type: [integer, "null"], description: Units on order from the factory and not yet received. `null` when we cannot tell right now. }
            backorderable: { type: boolean, description: "`true` when you can order more than we have in stock." }
    CatalogPage:
      allOf:
        - $ref: "#/components/schemas/PageMeta"
        - type: object
          required: [items]
          properties:
            items:
              type: array
              items: { $ref: "#/components/schemas/CatalogItem" }
    Order:
      type: object
      required: [order_number, po_number, customer_id, status, order_date, total_cents, currency, ship_method, updated_at]
      properties:
        order_number: { type: string }
        po_number: { type: [string, "null"] }
        customer_id: { type: integer, description: The account the order is on. A sub-account's id on a sub-account's order. }
        status: { type: [string, "null"] }
        order_date: { type: string, format: date }
        total_cents: { type: [integer, "null"] }
        currency: { type: [string, "null"] }
        ship_method: { type: [string, "null"] }
        updated_at: { type: string, format: date-time }
    OrdersPage:
      allOf:
        - $ref: "#/components/schemas/PageMeta"
        - type: object
          required: [orders]
          properties:
            orders:
              type: array
              items: { $ref: "#/components/schemas/Order" }
    Tracking:
      type: object
      required: [number, carrier, url]
      properties:
        number: { type: string }
        carrier: { type: [string, "null"] }
        url: { type: [string, "null"] }
    Shipment:
      type: object
      required: [shipment_number, order_number, po_number, customer_id, status, ship_date, ship_method, tracking, carrier_status, updated_at]
      properties:
        shipment_number: { type: [string, "null"] }
        order_number: { type: [string, "null"] }
        po_number: { type: [string, "null"] }
        customer_id: { type: integer }
        status: { type: [string, "null"] }
        ship_date: { type: string, format: date }
        ship_method: { type: [string, "null"] }
        tracking:
          type: array
          items: { $ref: "#/components/schemas/Tracking" }
        carrier_status: { type: [string, "null"], description: The last UPS status we recorded. }
        updated_at: { type: string, format: date-time }
    ShipmentsPage:
      allOf:
        - $ref: "#/components/schemas/PageMeta"
        - type: object
          required: [shipments]
          properties:
            shipments:
              type: array
              items: { $ref: "#/components/schemas/Shipment" }
    OrderWithShipments:
      allOf:
        - $ref: "#/components/schemas/Order"
        - type: object
          required: [shipments]
          properties:
            shipments:
              type: array
              items: { $ref: "#/components/schemas/Shipment" }
    OrderStatus:
      type: object
      required: [po_number, order_number, status, shipments]
      description: A placed order has total, total_cents, currency and order_date. An order still held, waiting out its release window, cancelled or refused at checkout has holds, release_after, subtotal_cents, currency, cancelled_at and error_code instead.
      properties:
        po_number: { type: string }
        order_number: { type: [string, "null"] }
        status: { type: [string, "null"], description: The order status once placed; held, pending_release, cancelled or failed before. }
        total: { type: [number, "null"], description: Dollars. Prefer `total_cents`. }
        total_cents: { type: [integer, "null"] }
        subtotal_cents: { type: [integer, "null"] }
        currency: { type: [string, "null"] }
        order_date: { type: [string, "null"], description: "Month/day/year, for example 9/25/2026." }
        holds:
          type: array
          items: { type: object }
        release_after: { type: [string, "null"], format: date-time }
        cancelled_at: { type: [string, "null"], format: date-time }
        error_code: { type: [string, "null"], description: The checkout's code when it refused the order. See the error table. }
        shipments:
          type: array
          items:
            type: object
            required: [shipment_number, ship_date, tracking]
            properties:
              shipment_number: { type: [string, "null"] }
              ship_date: { type: [string, "null"] }
              tracking:
                type: array
                items: { type: string }
    Invoice:
      type: object
      required: [id, invoice_number, customer_id, status, issued_on, due_on, overdue, terms, po_number, total_cents, amount_due_cents, currency, pdf_url, updated_at]
      properties:
        id: { type: integer }
        invoice_number: { type: string }
        customer_id: { type: integer }
        status: { type: [string, "null"] }
        issued_on: { type: string, format: date }
        due_on: { type: [string, "null"], format: date }
        overdue: { type: boolean, description: The amount due is above zero and the due date is before today in Pacific time. }
        terms: { type: [string, "null"] }
        po_number: { type: [string, "null"] }
        total_cents: { type: [integer, "null"] }
        amount_due_cents: { type: [integer, "null"] }
        currency: { type: [string, "null"] }
        pdf_url: { type: string, description: Path of the invoice PDF on this API. }
        updated_at: { type: string, format: date-time }
    InvoiceLine:
      type: object
      required: [item_id, sku, name, item_type, quantity, rate_cents, amount_cents, currency]
      properties:
        item_id: { type: [integer, "null"] }
        sku: { type: [string, "null"] }
        name: { type: string }
        item_type: { type: [string, "null"] }
        quantity: { type: number }
        rate_cents: { type: [integer, "null"] }
        amount_cents: { type: [integer, "null"] }
        currency: { type: [string, "null"] }
    InvoiceDetail:
      allOf:
        - $ref: "#/components/schemas/Invoice"
        - type: object
          required: [lines]
          properties:
            lines:
              type: array
              items: { $ref: "#/components/schemas/InvoiceLine" }
    InvoicesPage:
      allOf:
        - $ref: "#/components/schemas/PageMeta"
        - type: object
          required: [invoices]
          properties:
            invoices:
              type: array
              items: { $ref: "#/components/schemas/Invoice" }
    Credit:
      type: object
      required: [id, number, customer_id, status, issued_on, total_cents, amount_remaining_cents, currency, applied_to, updated_at]
      properties:
        id: { type: integer }
        number: { type: string }
        customer_id: { type: integer }
        status: { type: [string, "null"] }
        issued_on: { type: string, format: date }
        total_cents: { type: [integer, "null"] }
        amount_remaining_cents: { type: [integer, "null"], description: What is left to apply. }
        currency: { type: [string, "null"] }
        applied_to:
          type: array
          items:
            type: object
            required: [id, invoice_number]
            properties:
              id: { type: integer }
              invoice_number: { type: string }
        updated_at: { type: string, format: date-time }
    CreditMemosPage:
      allOf:
        - $ref: "#/components/schemas/PageMeta"
        - type: object
          required: [credit_memos]
          properties:
            credit_memos:
              type: array
              items: { $ref: "#/components/schemas/Credit" }
    PaymentsPage:
      allOf:
        - $ref: "#/components/schemas/PageMeta"
        - type: object
          required: [payments]
          properties:
            payments:
              type: array
              items: { $ref: "#/components/schemas/Credit" }
    Return:
      type: object
      required: [id, number, customer_id, status, requested_on, total_cents, currency, source_order, credit_memo, updated_at]
      properties:
        id: { type: integer }
        number: { type: string }
        customer_id: { type: integer }
        status: { type: [string, "null"] }
        requested_on: { type: string, format: date }
        total_cents: { type: [integer, "null"] }
        currency: { type: [string, "null"] }
        source_order:
          type: [object, "null"]
          required: [id, order_number]
          properties:
            id: { type: integer }
            order_number: { type: string }
        credit_memo:
          type: [object, "null"]
          required: [id, number, total_cents, currency]
          properties:
            id: { type: integer }
            number: { type: string }
            total_cents: { type: [integer, "null"] }
            currency: { type: [string, "null"] }
        updated_at: { type: string, format: date-time }
    ReturnsPage:
      allOf:
        - $ref: "#/components/schemas/PageMeta"
        - type: object
          required: [returns]
          properties:
            returns:
              type: array
              items: { $ref: "#/components/schemas/Return" }
    AgingBucket:
      type: object
      required: [count, amount_cents]
      properties:
        count: { type: integer, minimum: 0 }
        amount_cents: { type: integer }
    Statement:
      type: object
      required: [as_of, balance_cents, currency, aging, open_items]
      properties:
        as_of: { type: [string, "null"], format: date-time, description: How fresh the data is. }
        balance_cents: { type: integer }
        currency: { type: string }
        aging:
          type: object
          required: [current, days_1_30, days_31_60, days_61_90, days_91_plus]
          properties:
            current: { $ref: "#/components/schemas/AgingBucket" }
            days_1_30: { $ref: "#/components/schemas/AgingBucket" }
            days_31_60: { $ref: "#/components/schemas/AgingBucket" }
            days_61_90: { $ref: "#/components/schemas/AgingBucket" }
            days_91_plus: { $ref: "#/components/schemas/AgingBucket" }
        open_items:
          type: array
          items:
            type: object
            required: [kind, id, number, issued_on, due_on, days_past_due, amount_cents, currency]
            properties:
              kind: { type: string, enum: [invoice, credit_memo, payment] }
              id: { type: integer }
              number: { type: string }
              issued_on: { type: string, format: date }
              due_on: { type: [string, "null"], format: date }
              days_past_due: { type: [integer, "null"] }
              amount_cents: { type: integer, description: Credit memos and payments are negative. }
              currency: { type: string }
    ShipTo:
      type: object
      required: [address_id, label, addressee, addr1, addr2, city, state, zip, country, store_id, default_shipping]
      properties:
        address_id: { type: integer }
        label: { type: [string, "null"] }
        addressee: { type: [string, "null"] }
        addr1: { type: [string, "null"] }
        addr2: { type: [string, "null"] }
        city: { type: [string, "null"] }
        state: { type: [string, "null"] }
        zip: { type: [string, "null"] }
        country: { type: [string, "null"] }
        store_id: { type: [string, "null"], description: The store identifier Balaji holds on the address, when there is one. }
        default_shipping: { type: boolean }
    WebhookEvent:
      type: string
      enum: [order.created, order.updated, shipment.created, shipment.delivered, invoice.created, invoice.paid, credit_memo.created, payment.applied, return.created, return.updated]
    WebhookEndpoint:
      type: object
      required: [id, url, description, events, mode, secret_prefix, status, disabled_reason, disabled_at, failing_since, last_success_at, created_at]
      properties:
        id: { type: string }
        url: { type: string }
        description: { type: [string, "null"] }
        events:
          type: array
          items: { $ref: "#/components/schemas/WebhookEvent" }
        mode: { type: string, enum: [test, live] }
        secret_prefix: { type: string, description: The first characters of the signing secret, to tell secrets apart. }
        status: { type: string, enum: [active, disabled] }
        disabled_reason: { type: [string, "null"] }
        disabled_at: { type: [string, "null"] }
        failing_since: { type: [string, "null"], description: When deliveries started failing. After 24 hours of failures the endpoint is disabled. }
        last_success_at: { type: [string, "null"] }
        created_at: { type: string }
    WebhookDelivery:
      type: object
      required: [id, event_id, event_type, test, attempt, status_code, ok, error, duration_ms, created_at]
      properties:
        id: { type: integer }
        event_id: { type: string }
        event_type: { $ref: "#/components/schemas/WebhookEvent" }
        test: { type: boolean }
        attempt: { type: integer }
        status_code: { type: [integer, "null"] }
        ok: { type: boolean }
        error: { type: [string, "null"] }
        duration_ms: { type: integer }
        created_at: { type: string }
    StoreMapRequest:
      type: object
      required: [stores]
      properties:
        stores:
          type: array
          maxItems: 5000
          items:
            type: object
            required: [store_id, address_id]
            properties:
              store_id: { type: string, minLength: 1, maxLength: 64, description: Your store code. Printable ASCII. }
              address_id: { type: integer, minimum: 1 }
    StoreMap:
      type: object
      required: [stores]
      properties:
        stores:
          type: array
          items:
            type: object
            required: [store_id, address_id, updated_at]
            properties:
              store_id: { type: string }
              address_id: { type: integer }
              updated_at: { type: string, format: date-time }
    OrderRequest:
      type: object
      required: [po_number, ship_to, lines]
      properties:
        po_number: { type: string, minLength: 1, maxLength: 45, description: Your PO number, printable ASCII. One PO number places one order. }
        ship_to:
          type: object
          description: Name a `store_id` once you have a store map. Without a map, name an `address_id`. With a map you may send both; they must agree.
          properties:
            store_id: { type: string }
            address_id: { type: integer, minimum: 1 }
        lines:
          type: array
          minItems: 1
          maxItems: 500
          items:
            type: object
            required: [quantity]
            properties:
              sku: { type: string, maxLength: 80, description: The Balaji SKU. Send `sku` or `upc`, not both. }
              upc: { type: string, pattern: "^\\d{8,14}$" }
              quantity: { type: integer, minimum: 1, maximum: 99999 }
              unit_price: { type: number, minimum: 0, description: The price on your PO. Never charged. If it differs from our price the line comes back with `price_flag` true. }
              line_ref: { type: string, maxLength: 60, description: Your own line number. Sent back so you can match lines. }
        notes: { type: string, maxLength: 500 }
    OrderResultLine:
      type: object
      required: [line, line_ref, sku, requested_quantity, quantity, case_pack, unit_price, po_unit_price, price_flag]
      properties:
        line: { type: integer, minimum: 0 }
        line_ref: { type: [string, "null"] }
        sku: { type: string }
        requested_quantity: { type: integer }
        quantity: { type: integer, description: What checkout would order. A quantity that is not a whole number of case packs rounds up and the order is held with CASE_PACK_ROUNDED. }
        case_pack: { type: integer }
        unit_price: { type: [number, "null"], description: Our price, in dollars. }
        po_unit_price: { type: [number, "null"] }
        price_flag: { type: boolean }
    OrderResult:
      type: object
      required: [mode, result, po_number]
      properties:
        mode: { type: string, enum: [test, live] }
        result: { type: string, enum: [validated, dry_run, placed, already_placed, held, pending_release, placing, failed] }
        placed: { type: boolean }
        po_number: { type: string }
        ship_to: { $ref: "#/components/schemas/ShipTo" }
        store_id: { type: [string, "null"] }
        shipping:
          type: object
          required: [method, estimated_rate]
          properties:
            method: { type: string }
            estimated_rate: { type: number }
        lines:
          type: array
          items: { $ref: "#/components/schemas/OrderResultLine" }
        subtotal: { type: number }
        subtotal_cents: { type: integer }
        currency: { type: string }
        price_flags: { type: integer }
        holds:
          type: array
          items:
            type: object
            required: [code, message]
            properties:
              code: { type: string, enum: [LINE_QUANTITY_ABOVE_HISTORY, ORDER_TOTAL_ABOVE_HISTORY, CASE_PACK_ROUNDED] }
              message: { type: string }
              line: { type: integer, minimum: 0, description: Zero-based position of the line, for a line hold. }
              line_ref: { type: [string, "null"] }
        order:
          type: object
          description: The placed order, or for `already_placed` the order that already exists.
          properties:
            order_number: { type: [string, "null"] }
            status: { type: [string, "null"] }
            total: { type: [number, "null"] }
            order_date: { type: [string, "null"] }
            subtotal_cents: { type: [integer, "null"] }
            currency: { type: [string, "null"] }
        release_after: { type: string, format: date-time, description: When a pending_release order goes to the warehouse queue. Cancel before then. }
        request_id: { type: string }
    OAuthToken:
      type: object
      required: [access_token, token_type, expires_in, refresh_token]
      properties:
        access_token: { type: string }
        token_type: { const: Bearer }
        expires_in: { type: integer }
        refresh_token: { type: string }
        scope: { type: string }
    OAuthError:
      type: object
      required: [error]
      properties:
        error: { type: string }
        error_description: { type: string }
