# GENERATED from ../openapi/vault-api-v1.source.yaml.
# Do not edit this file directly; run bun run openapi:generate.
openapi: 3.0.3
info:
  contact:
    name: Stashtab Platform Team
  description: "The Vault by Stashtab API lets partner applications declare expected\narrivals, observe every physical item held in custody, request parcel\nfulfillment or withdrawal, transfer items through supported exit paths, and\nreceive signed event notifications. Responses are suitable for mirroring into\npartner systems.\n\n## Authentication\n\nEvery request is authenticated with an API key in the `X-API-Key` header.\nKeys are managed in the partner console, are named, may carry an expiry, and\ncan be deactivated at any time. Keys must only be used server-side.\n\n## Idempotency\n\nEvery POST accepts an `Idempotency-Key` header. Reusing the same key on the\nsame endpoint returns the original result instead of repeating the action.\nKeys are retained long enough for any reasonable retry policy.\n\n## Forward compatibility\n\nWithin v1, new fields, enum values, error codes, item types, and endpoints are\nnon-breaking additions. Clients must tolerate values they do not yet know.\nBreaking changes use a new version prefix and are announced in advance.\n\n## Environments & data partitioning\n\nProduction and sandbox use separate hosts and fully partitioned data.\n`sk_live_*` keys work only on production and `sk_test_*` keys work only on\nsandbox. Using a key on the wrong host returns `401 environment_mismatch`.\nA QID from the other environment returns a plain `404` so existence is not\ndisclosed.\n\n## Error codes\n\nErrors use a stable machine-readable vocabulary: `validation_error`,\n`state_conflict`, `environment_mismatch`, `unsupported_destination`,\n`invalid_api_key`, `not_found`, and `rate_limited`. Each operation lists\nits possible HTTP error codes in `x-error-codes`; partial-success batch responses\ncarry row-result codes in their result body.\n\n## Pagination\n\nBounded collections use a page envelope with `page` and `page_size` query\nparameters. The event stream uses the `starting_after` cursor instead.\n\n## Rate limits\n\nRequests are rate limited per API key. A `429` response carries\n`Retry-After`, `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and\n`X-RateLimit-Reset`. Default limits are confirmed during onboarding and\nsized to the integration.\n\n## Item lifecycle\n\nItems move through intake and custody before they ship, withdraw, or complete\na sell-back transfer. Returned items re-enter at `received` and repeat intake.\nThe status vocabulary is additive under the forward-compatibility policy.\n\n## Fulfillment lifecycle\n\nFulfillments move through `pending`, `processing`, `shipped`, and\n`delivered`, or end as `cancelled`, `exception`, or `returned`.\n`processing_stage` may be null when granular telemetry is unavailable;\n`status` is always authoritative."
  title: Vault by Stashtab API
  version: 1.0.0
servers:
  - description: Production
    url: https://api.vault.stashtab.gg/v1
  - description: Sandbox — deterministic fixture data with no physical product required
    url: https://sandbox.api.vault.stashtab.gg/v1
security:
  - ApiKeyAuth:
      []
tags:
  - description: Inspect the authenticated partner organization and API key metadata.
    name: Account
  - description: "Digitization completions, with the same payloads available through signed events."
    name: Intake
  - description: Declare immutable expected arrivals and reconcile physical intake against them.
    name: Manifests
  - description: Define catalog records before product inventory reaches the vault.
    name: Products
  - description: Inspect physical-item custody records and SKU-level computed stock.
    name: Inventory
  - description: Create parcel shipments or withdrawals and follow their execution.
    name: Fulfillment
  - description: Request and follow sell-back transfers for items in custody.
    name: Transfers
  - description: Manage signed event delivery and replay the event stream.
    name: Webhooks
  - description: "Sandbox API keys are issued during partner onboarding. Sandbox access is not self-service, but the full contract is public so an integration can be planned before keys are issued."
    name: Sandbox
paths:
  /intakes:
    get:
      operationId: listIntakeEvents
      tags:
        - Intake
      summary: List intake events
      description: "Everything that completed digitization in a time window — e.g. \"all\nmy intakes for today\". Each entry carries the same payload as the\n`item.intake.completed` webhook, so pull and push are interchangeable.\n"
      parameters:
        - $ref: "#/components/parameters/Page"
        - $ref: "#/components/parameters/PageSize"
        - name: intake_after
          in: query
          description: Only intakes completed at or after this time.
          schema:
            type: string
            format: date-time
          example: 2026-07-02T00:00:00Z
        - name: intake_before
          in: query
          description: Only intakes completed before this time.
          schema:
            type: string
            format: date-time
        - name: manifest_id
          in: query
          description: Only intakes matched to lines of this manifest.
          schema:
            type: string
            format: uuid
        - name: cert_number
          in: query
          description: Exact grader certification number.
          schema:
            type: string
      responses:
        "200":
          description: "Paginated intake events, newest first."
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/PaginationEnvelope"
                  - type: object
                    properties:
                      results:
                        type: array
                        items:
                          $ref: "#/components/schemas/IntakeEvent"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
      x-error-codes:
        - environment_mismatch
        - invalid_api_key
        - rate_limited
        - validation_error
  /manifests:
    post:
      operationId: createManifest
      tags:
        - Manifests
      summary: Submit an expected manifest
      description: "The order sheet: what you bought and expect to arrive. Each line\ncarries as much identifying detail as you have — a cert number when\nknown, otherwise a free-text description. Incoming items are matched\nagainst open manifest lines during intake.\n\nPrefer CSV? `POST /manifests/csv` accepts the same data as a file\nupload.\n"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ManifestCreate"
            example:
              expected_arrival_end: 2026-07-24
              expected_arrival_start: 2026-07-18
              lines:
                - cert_number: "48291077"
                  description: 2018 Panini Prizm Luka Doncic
                  external_ref: ITEM-88231
                  grader: PSA
                  quantity: 1
                - cert_number: null
                  description: Sealed Collector Box
                  external_ref: ITEM-99001
                  grader: null
                  quantity: 6
                  sku: SEALED-BOX-ALPHA
              reference: BUY-2026-07-0031
              source: distributor
      responses:
        "201":
          description: Manifest accepted and open for matching.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Manifest"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      x-error-codes:
        - environment_mismatch
        - invalid_api_key
        - rate_limited
        - validation_error
    get:
      operationId: listManifests
      tags:
        - Manifests
      summary: List manifests
      parameters:
        - $ref: "#/components/parameters/Page"
        - $ref: "#/components/parameters/PageSize"
        - name: status
          in: query
          schema:
            $ref: "#/components/schemas/ManifestStatus"
        - name: reference
          in: query
          description: Exact match on your reference.
          schema:
            type: string
      responses:
        "200":
          description: "Paginated manifests, newest first."
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/PaginationEnvelope"
                  - type: object
                    properties:
                      results:
                        type: array
                        items:
                          $ref: "#/components/schemas/Manifest"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
      x-error-codes:
        - environment_mismatch
        - invalid_api_key
        - rate_limited
        - validation_error
  /manifests/csv:
    post:
      operationId: uploadManifestCsv
      tags:
        - Manifests
      summary: Submit an expected manifest (CSV upload)
      description: "File-upload alternative to `POST /manifests` for teams that live in\nspreadsheets. Expected columns: `external_ref`, `description`, `sku`,\n`grader`, `cert_number`, `quantity`. Product rows provide a catalog `sku`;\nslab rows provide `grader` and `cert_number`; a row never mixes both forms.\nOne row per expected item. Valid rows are accepted into one manifest and invalid\nrows are returned independently.\n"
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required:
                - file
                - reference
              properties:
                file:
                  type: string
                  description: "CSV file, UTF-8, header row required."
                  format: binary
                reference:
                  type: string
                  description: Your reference for this purchase batch.
                source:
                  $ref: "#/components/schemas/ManifestSource"
      responses:
        "201":
          description: Rows processed independently into one manifest.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ManifestCsvBatchResult"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      x-error-codes:
        - environment_mismatch
        - invalid_api_key
        - rate_limited
        - validation_error
  "/manifests/{manifest_id}":
    get:
      operationId: getManifest
      tags:
        - Manifests
      summary: Get a manifest with per-line status
      parameters:
        - name: manifest_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: "The manifest, including current per-line match status."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ManifestDetail"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/TooManyRequests"
      x-error-codes:
        - environment_mismatch
        - invalid_api_key
        - not_found
        - rate_limited
  "/manifests/{manifest_id}/reconciliation":
    get:
      operationId: getReconciliationReport
      tags:
        - Manifests
      summary: Get the reconciliation report for a manifest
      description: "Did we get what you bought? Per-line matched / missing /\ncondition-mismatch status plus anything that arrived attributed to\nthis manifest that you did not list. The\n`manifest.reconciliation.completed` webhook fires when this report is\nfinalized, so consuming it can be purely automated.\n"
      parameters:
        - name: manifest_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: The current reconciliation report.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ReconciliationReport"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/TooManyRequests"
      x-error-codes:
        - environment_mismatch
        - invalid_api_key
        - not_found
        - rate_limited
  /items:
    get:
      operationId: listItems
      tags:
        - Inventory
      summary: List items
      description: "Everything we hold for you — stored, in fulfillment, or in flight —\npaginated and filterable. Items appear here the moment they complete\nintake; nothing exists until it is in the warehouse system.\n"
      parameters:
        - $ref: "#/components/parameters/Page"
        - $ref: "#/components/parameters/PageSize"
        - name: status
          in: query
          schema:
            $ref: "#/components/schemas/ItemStatus"
        - name: sku
          in: query
          description: Filter by SKU (partial match).
          schema:
            type: string
        - name: cert_number
          in: query
          description: Exact grader certification number.
          schema:
            type: string
        - name: grader
          in: query
          schema:
            type: string
          example: PSA
        - name: manifest_id
          in: query
          description: Only items matched to lines of this manifest.
          schema:
            type: string
            format: uuid
        - name: search
          in: query
          description: Free-text search over name and SKU.
          schema:
            type: string
        - name: intake_after
          in: query
          schema:
            type: string
            format: date-time
        - name: intake_before
          in: query
          schema:
            type: string
            format: date-time
        - name: sort_by
          in: query
          schema:
            type: string
            enum:
              - intake_at
              - updated_at
            default: intake_at
        - name: sort_dir
          in: query
          schema:
            type: string
            enum:
              - asc
              - desc
            default: desc
      responses:
        "200":
          description: Paginated items.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/PaginationEnvelope"
                  - type: object
                    properties:
                      results:
                        type: array
                        items:
                          $ref: "#/components/schemas/Item"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
      x-error-codes:
        - environment_mismatch
        - invalid_api_key
        - rate_limited
        - validation_error
  /items/summary:
    get:
      operationId: getItemSummary
      tags:
        - Inventory
      summary: Aggregate counts by status
      description: "Ops-dashboard rollup: how many items we hold for you in each lifecycle status."
      responses:
        "200":
          description: Counts by status.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ItemSummary"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
      x-error-codes:
        - environment_mismatch
        - invalid_api_key
        - rate_limited
  "/items/{qid}":
    get:
      operationId: getItem
      tags:
        - Inventory
      summary: Get an item by QID
      description: "Full detail for one item: identity, lifecycle status, assets, audit timestamps, and the active fulfillment reference when one exists."
      parameters:
        - name: qid
          in: path
          required: true
          schema:
            $ref: "#/components/schemas/QID"
      responses:
        "200":
          description: The item.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Item"
              examples:
                gradedSlab:
                  summary: Graded slab
                  value:
                    active_fulfillment_id: null
                    card_number: "280"
                    category: sports-card
                    cert_number: "48291077"
                    external_ref: ITEM-88231
                    grade: "10"
                    grader: PSA
                    image_assets:
                      - format: webp
                        height: 1200
                        url: https://assets.vault.stashtab.gg/items/graded-front.webp
                        variant: front
                        width: 800
                    intake_at: 2026-07-18T14:20:00Z
                    manifest_line_id: d6a4a5c8-168e-4a31-b44a-3813795820b0
                    model_3d: null
                    name: 2018 Panini Prizm Luka Doncic
                    qid: 7c9e6679-7425-40de-944b-e07fc1f90ae7
                    set: 2018-19 Panini Prizm
                    sku: PSA-48291077
                    status: available
                    type: graded_slab
                    updated_at: 2026-07-18T14:20:00Z
                product:
                  summary: Catalog product unit
                  value:
                    active_fulfillment_id: null
                    card_number: null
                    category: sealed-product
                    cert_number: null
                    external_ref: ITEM-99001
                    grade: null
                    grader: null
                    image_assets:
                      - format: webp
                        height: 900
                        url: https://assets.vault.stashtab.gg/catalog/sealed-box-alpha.webp
                        variant: front
                        width: 900
                    intake_at: 2026-07-18T14:30:00Z
                    manifest_line_id: dfcb4636-ff6e-401d-912b-c76e4f25b480
                    model_3d: null
                    name: Sealed Collector Box
                    qid: 11fc7a13-37b0-4cf0-a992-2dafe918776a
                    set: null
                    sku: SEALED-BOX-ALPHA
                    status: available
                    type: product
                    updated_at: 2026-07-18T14:30:00Z
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/TooManyRequests"
      x-error-codes:
        - environment_mismatch
        - invalid_api_key
        - not_found
        - rate_limited
  /fulfillments:
    post:
      operationId: createFulfillment
      tags:
        - Fulfillment
      summary: Create a fulfillment
      description: "Address inventory by exact QIDs or SKU-and-quantity lines. Standard\nfulfillment uses a recipient shipping address; withdrawal uses the\norganization's primary verified address or a `verified_address_id` and never\naccepts a free-form address. Everything downstream — allocation, warehouse\norder, pick, pack, verification, label, and carrier handoff — happens on our\nside, and progress flows back through `GET /fulfillments/{fulfillment_id}`\nand the `fulfillment.status.changed` webhook.\n\nSend an `Idempotency-Key` header; retries with the same key return the\noriginal fulfillment instead of shipping twice.\n"
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/FulfillmentRequest"
            example:
              external_ref: ORDER-20419
              qids:
                - 3f2c8a1d-6b0e-4d2f-9a7c-5e8b1d4f6a92
              shipping_address:
                city: Austin
                country: US
                recipient_name: Jordan Alvarez
                state: TX
                street: 480 Cedar St
                street_2: Apt 12
                zip: "78701"
              special_instructions: Protect the item from movement inside the parcel.
              type: standard
      responses:
        "201":
          description: Fulfillment accepted and queued for the warehouse.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Fulfillment"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          description: "An item owned by this partner is not available to ship — for example it already has an active fulfillment, or it has not completed intake. Missing, foreign-partner, and cross-environment QIDs or verified addresses return the same plain 404."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                message: Item 3f2c8a1d-6b0e-4d2f-9a7c-5e8b1d4f6a92 already has an active fulfillment.
                code: state_conflict
                extra:
                  {}
        "422":
          $ref: "#/components/responses/UnprocessableEntity"
        "429":
          $ref: "#/components/responses/TooManyRequests"
      x-error-codes:
        - environment_mismatch
        - invalid_api_key
        - not_found
        - rate_limited
        - state_conflict
        - unsupported_destination
        - validation_error
    get:
      operationId: listFulfillments
      tags:
        - Fulfillment
      summary: List fulfillments
      description: "Filterable, paginated fulfillment history. Look up by QID (single or bulk via `qids`), by your own `external_ref`, by status, or by date — this doubles as the bulk status endpoint."
      parameters:
        - $ref: "#/components/parameters/Page"
        - $ref: "#/components/parameters/PageSize"
        - name: status
          in: query
          schema:
            $ref: "#/components/schemas/FulfillmentStatus"
        - name: qid
          in: query
          description: Fulfillments containing this item.
          schema:
            $ref: "#/components/schemas/QID"
        - name: qids
          in: query
          description: Comma-separated QIDs for bulk status lookup.
          schema:
            type: string
          example: "3f2c8a1d-6b0e-4d2f-9a7c-5e8b1d4f6a92,7c9e6679-7425-40de-944b-e07fc1f90ae7"
        - name: external_ref
          in: query
          description: Exact match on your reference.
          schema:
            type: string
        - name: created_after
          in: query
          schema:
            type: string
            format: date-time
        - name: created_before
          in: query
          schema:
            type: string
            format: date-time
      responses:
        "200":
          description: "Paginated fulfillments, newest first."
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/PaginationEnvelope"
                  - type: object
                    properties:
                      results:
                        type: array
                        items:
                          $ref: "#/components/schemas/Fulfillment"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
      x-error-codes:
        - environment_mismatch
        - invalid_api_key
        - rate_limited
        - validation_error
  "/fulfillments/{fulfillment_id}":
    get:
      operationId: getFulfillment
      tags:
        - Fulfillment
      summary: Get a fulfillment
      description: "Current status plus everything shipping-related you would mirror to your customer: carrier, service, tracking number, tracking URL."
      parameters:
        - name: fulfillment_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: The fulfillment.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Fulfillment"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/TooManyRequests"
      x-error-codes:
        - environment_mismatch
        - invalid_api_key
        - not_found
        - rate_limited
  "/fulfillments/{fulfillment_id}/cancel":
    post:
      operationId: cancelFulfillment
      tags:
        - Fulfillment
      summary: Cancel a fulfillment (pre-pick)
      description: Cancels a fulfillment that has not yet been picked. Once the item is picked the fulfillment can no longer be cancelled and this returns a conflict.
      parameters:
        - name: fulfillment_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        "200":
          description: "Fulfillment cancelled; items return to `available`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Fulfillment"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          description: Too late — the fulfillment is already past pick.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                message: Fulfillment is already being processed and can no longer be cancelled.
                code: state_conflict
                extra:
                  {}
        "429":
          $ref: "#/components/responses/TooManyRequests"
      x-error-codes:
        - environment_mismatch
        - invalid_api_key
        - not_found
        - rate_limited
        - state_conflict
  /webhook-subscriptions:
    post:
      operationId: createWebhookSubscription
      tags:
        - Webhooks
      summary: Register a webhook endpoint
      description: "Register an HTTPS endpoint and the event types it should receive. The\nsigning `secret` is generated by us and returned **once** in this\nresponse — store it and use it to verify the `X-Vault-Signature`\nheader on deliveries (see the Webhooks section of each event below).\n"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/WebhookSubscriptionCreate"
            example:
              url: https://hooks.yourapp.com/vault
              event_types:
                - item.intake.completed
                - fulfillment.status.changed
                - fulfillment.issue.raised
      responses:
        "201":
          description: "Subscription created. `secret` is shown only once."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookSubscriptionWithSecret"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      x-error-codes:
        - environment_mismatch
        - invalid_api_key
        - rate_limited
        - validation_error
    get:
      operationId: listWebhookSubscriptions
      tags:
        - Webhooks
      summary: List webhook subscriptions
      responses:
        "200":
          description: All subscriptions for your account.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/WebhookSubscription"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
      x-error-codes:
        - environment_mismatch
        - invalid_api_key
        - rate_limited
  "/webhook-subscriptions/{subscription_id}":
    get:
      operationId: getWebhookSubscription
      tags:
        - Webhooks
      summary: Get a webhook subscription
      parameters:
        - name: subscription_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: The subscription (secret is never returned again).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookSubscription"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/TooManyRequests"
      x-error-codes:
        - environment_mismatch
        - invalid_api_key
        - not_found
        - rate_limited
    delete:
      operationId: deleteWebhookSubscription
      tags:
        - Webhooks
      summary: Delete a webhook subscription
      parameters:
        - name: subscription_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "204":
          description: Subscription deleted; no further deliveries.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/TooManyRequests"
      x-error-codes:
        - environment_mismatch
        - invalid_api_key
        - not_found
        - rate_limited
    patch:
      operationId: updateWebhookSubscription
      parameters:
        - in: path
          name: subscription_id
          required: true
          schema:
            format: uuid
            type: string
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/WebhookSubscriptionUpdate"
        required: true
      responses:
        "200":
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookSubscription"
          description: Subscription updated.
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/TooManyRequests"
      summary: Update a webhook subscription
      tags:
        - Webhooks
      x-error-codes:
        - environment_mismatch
        - invalid_api_key
        - not_found
        - rate_limited
        - validation_error
  /events:
    get:
      description: Replay signed events in creation order using an event ID cursor.
      operationId: listEvents
      parameters:
        - $ref: "#/components/parameters/StartingAfter"
        - $ref: "#/components/parameters/PageSize"
        - in: query
          name: type
          schema:
            $ref: "#/components/schemas/WebhookEventType"
      responses:
        "200":
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CursorEnvelope"
          description: Cursor page of events.
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
      summary: List events
      tags:
        - Webhooks
      x-error-codes:
        - environment_mismatch
        - invalid_api_key
        - rate_limited
        - validation_error
  "/sandbox/items/{qid}/advance":
    post:
      operationId: sandboxAdvanceItem
      tags:
        - Sandbox
      summary: "Advance an item's lifecycle (sandbox only)"
      description: "Sandbox-only. Moves a fixture item to the given lifecycle status so\nyou can simulate intake → available → fulfillment → delivered without\nphysical product. Emits the same webhooks the production system\nwould. Returns `404` in production.\n"
      parameters:
        - name: qid
          in: path
          required: true
          schema:
            $ref: "#/components/schemas/QID"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - to_status
              properties:
                to_status:
                  $ref: "#/components/schemas/ItemStatus"
            example:
              to_status: available
      responses:
        "200":
          description: The item in its new status.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Item"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/TooManyRequests"
      x-error-codes:
        - environment_mismatch
        - invalid_api_key
        - not_found
        - rate_limited
        - validation_error
  "/manifests/{manifest_id}/close":
    post:
      description: "Declare that no more items are expected and begin reconciliation. Manifests are immutable after submission. An open manifest also closes automatically after `expected_arrival_end` plus a qualitative grace window."
      operationId: closeManifest
      parameters:
        - in: path
          name: manifest_id
          required: true
          schema:
            format: uuid
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        "200":
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ManifestDetail"
          description: Manifest closed for reconciliation.
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
      summary: Close a manifest
      tags:
        - Manifests
      x-error-codes:
        - environment_mismatch
        - invalid_api_key
        - not_found
        - rate_limited
        - state_conflict
        - validation_error
  /fulfillments/csv:
    post:
      description: "Upload a UTF-8 CSV with a header row. Columns: `qid`, `sku`, `quantity`, `recipient_name`, `street`, `street_2`, `city`, `state`, `zip`, `country`, `external_ref`, `special_instructions`, `deliver_by`. Each row provides either `qid` or `sku` plus `quantity`, never both forms. Missing, foreign-partner, and cross-environment QIDs fail that row with the same generic `not_found` code and no existence detail; unsupported destinations fail that row with `unsupported_destination`."
      operationId: uploadFulfillmentsCsv
      requestBody:
        content:
          multipart/form-data:
            schema:
              properties:
                file:
                  format: binary
                  type: string
              required:
                - file
              type: object
        required: true
      responses:
        "200":
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FulfillmentCsvBatchResult"
          description: Rows processed independently.
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
      summary: Create fulfillments from CSV
      tags:
        - Fulfillment
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      x-error-codes:
        - environment_mismatch
        - invalid_api_key
        - rate_limited
        - validation_error
  /transfers:
    get:
      operationId: listTransfers
      parameters:
        - $ref: "#/components/parameters/Page"
        - $ref: "#/components/parameters/PageSize"
        - in: query
          name: status
          schema:
            $ref: "#/components/schemas/TransferStatus"
      responses:
        "200":
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/PaginationEnvelope"
                  - properties:
                      results:
                        items:
                          $ref: "#/components/schemas/Transfer"
                        type: array
                    type: object
          description: Paginated transfers.
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
      summary: List transfers
      tags:
        - Transfers
      x-error-codes:
        - environment_mismatch
        - invalid_api_key
        - rate_limited
        - validation_error
    post:
      description: "Request sell-back to Stashtab. Items remain held in pending_transfer until the transfer completes, declines, or is cancelled."
      operationId: createTransfer
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TransferCreate"
        required: true
      responses:
        "201":
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Transfer"
          description: Transfer requested.
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
      summary: Request a sell-back transfer
      tags:
        - Transfers
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      x-error-codes:
        - environment_mismatch
        - invalid_api_key
        - not_found
        - rate_limited
        - state_conflict
        - validation_error
  "/transfers/{transfer_id}":
    get:
      operationId: getTransfer
      parameters:
        - in: path
          name: transfer_id
          required: true
          schema:
            format: uuid
            type: string
      responses:
        "200":
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Transfer"
          description: Transfer detail.
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/TooManyRequests"
      summary: Get a transfer
      tags:
        - Transfers
      x-error-codes:
        - environment_mismatch
        - invalid_api_key
        - not_found
        - rate_limited
        - validation_error
  "/transfers/{transfer_id}/cancel":
    post:
      description: Cancel a requested transfer. Repeating cancellation of an already-cancelled transfer returns the same record.
      operationId: cancelTransfer
      parameters:
        - in: path
          name: transfer_id
          required: true
          schema:
            format: uuid
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        "200":
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Transfer"
          description: Transfer cancelled.
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
      summary: Cancel a transfer
      tags:
        - Transfers
      x-error-codes:
        - environment_mismatch
        - invalid_api_key
        - not_found
        - rate_limited
        - state_conflict
        - validation_error
  /account:
    get:
      description: "Use this as the first call to verify organization, environment, and API key metadata."
      operationId: getAccount
      responses:
        "200":
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Account"
          description: Authenticated account metadata.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
      summary: Get the authenticated account
      tags:
        - Account
      x-error-codes:
        - environment_mismatch
        - invalid_api_key
        - rate_limited
  "/webhook-subscriptions/{subscription_id}/rotate-secret":
    post:
      description: Return a replacement signing secret once. The prior secret remains valid briefly for rotation.
      operationId: rotateWebhookSubscriptionSecret
      parameters:
        - in: path
          name: subscription_id
          required: true
          schema:
            format: uuid
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        "200":
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookSubscriptionWithSecret"
          description: Subscription with the replacement secret.
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/TooManyRequests"
      summary: Rotate a webhook signing secret
      tags:
        - Webhooks
      x-error-codes:
        - environment_mismatch
        - invalid_api_key
        - not_found
        - rate_limited
        - validation_error
  "/webhook-subscriptions/{subscription_id}/test":
    post:
      description: Send a signed synthetic event to this subscription.
      operationId: testWebhookSubscription
      parameters:
        - in: path
          name: subscription_id
          required: true
          schema:
            format: uuid
            type: string
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        "202":
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookEvent"
          description: Synthetic event accepted for delivery.
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/TooManyRequests"
      summary: Test a webhook subscription
      tags:
        - Webhooks
      x-error-codes:
        - environment_mismatch
        - invalid_api_key
        - not_found
        - rate_limited
        - validation_error
  "/events/{event_id}":
    get:
      operationId: getEvent
      parameters:
        - in: path
          name: event_id
          required: true
          schema:
            format: uuid
            type: string
      responses:
        "200":
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookEvent"
          description: Event detail.
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/TooManyRequests"
      summary: Get an event
      tags:
        - Webhooks
      x-error-codes:
        - environment_mismatch
        - invalid_api_key
        - not_found
        - rate_limited
        - validation_error
  /products:
    get:
      operationId: listProducts
      parameters:
        - $ref: "#/components/parameters/Page"
        - $ref: "#/components/parameters/PageSize"
        - description: Filter by SKU.
          in: query
          name: sku
          schema:
            type: string
      responses:
        "200":
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/PaginationEnvelope"
                  - properties:
                      results:
                        items:
                          $ref: "#/components/schemas/Product"
                        type: array
                    type: object
          description: Paginated products.
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
      summary: List products
      tags:
        - Products
      x-error-codes:
        - environment_mismatch
        - invalid_api_key
        - rate_limited
        - validation_error
    post:
      operationId: createProduct
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ProductCreate"
        required: true
      responses:
        "201":
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Product"
          description: Catalog product created.
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
      summary: Create a product
      tags:
        - Products
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      x-error-codes:
        - environment_mismatch
        - invalid_api_key
        - rate_limited
        - state_conflict
        - validation_error
  "/products/{product_id}":
    get:
      operationId: getProduct
      parameters:
        - in: path
          name: product_id
          required: true
          schema:
            format: uuid
            type: string
      responses:
        "200":
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Product"
          description: Catalog product detail.
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/TooManyRequests"
      summary: Get a product
      tags:
        - Products
      x-error-codes:
        - environment_mismatch
        - invalid_api_key
        - not_found
        - rate_limited
        - validation_error
    patch:
      operationId: updateProduct
      parameters:
        - in: path
          name: product_id
          required: true
          schema:
            format: uuid
            type: string
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ProductUpdate"
        required: true
      responses:
        "200":
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Product"
          description: Catalog product updated.
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/TooManyRequests"
      summary: Update a product
      tags:
        - Products
      x-error-codes:
        - environment_mismatch
        - invalid_api_key
        - not_found
        - rate_limited
        - state_conflict
        - validation_error
  /products/csv:
    post:
      description: "Upload a UTF-8 CSV with a header row. Columns: `name`, `sku`, `upc`, `length`, `width`, `height`, `dimension_unit`, `weight`, `weight_unit`, `image_url`. Valid rows are created while invalid rows return field-level errors."
      operationId: uploadProductsCsv
      requestBody:
        content:
          multipart/form-data:
            schema:
              properties:
                file:
                  format: binary
                  type: string
              required:
                - file
              type: object
        required: true
      responses:
        "200":
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ProductCsvBatchResult"
          description: Rows processed independently.
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
      summary: Import products from CSV
      tags:
        - Products
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      x-error-codes:
        - environment_mismatch
        - invalid_api_key
        - rate_limited
        - validation_error
  /inventory:
    get:
      description: Computed SKU stock levels with no warehouse dimension.
      operationId: listInventoryLevels
      parameters:
        - $ref: "#/components/parameters/Page"
        - $ref: "#/components/parameters/PageSize"
        - in: query
          name: sku
          schema:
            type: string
      responses:
        "200":
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/PaginationEnvelope"
                  - properties:
                      results:
                        items:
                          $ref: "#/components/schemas/InventoryLevel"
                        type: array
                    type: object
          description: Paginated SKU inventory levels.
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
      summary: List SKU inventory levels
      tags:
        - Inventory
      x-error-codes:
        - environment_mismatch
        - invalid_api_key
        - rate_limited
        - validation_error
x-webhooks:
  item.intake.completed:
    post:
      operationId: eventItemIntakeCompleted
      parameters:
        - $ref: "#/components/parameters/XVaultEventID"
        - $ref: "#/components/parameters/XVaultSignature"
      security:
        []
      tags:
        - Webhooks
      summary: "Event: item.intake.completed"
      description: "Fired when an item completes digitization and becomes part of partner inventory.\nThe payload matches GET /intakes entries. Deliveries carry X-Vault-Event-Id\nand X-Vault-Signature headers. Verify signatures and timestamp freshness,\nacknowledge promptly, and recover missed deliveries from GET /events."
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/WebhookItemIntakeCompleted"
      responses:
        2XX:
          description: Return any 2xx quickly to acknowledge receipt.
  item.status.changed:
    post:
      operationId: eventItemStatusChanged
      parameters:
        - $ref: "#/components/parameters/XVaultEventID"
        - $ref: "#/components/parameters/XVaultSignature"
      security:
        []
      tags:
        - Webhooks
      summary: "Event: item.status.changed"
      description: "Fired on every item lifecycle transition, including pending-transfer, released, and withdrawn outcomes and returned items re-entering at received."
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/WebhookItemStatusChanged"
      responses:
        2XX:
          description: Return any 2xx quickly to acknowledge receipt.
  fulfillment.status.changed:
    post:
      operationId: eventFulfillmentStatusChanged
      parameters:
        - $ref: "#/components/parameters/XVaultEventID"
        - $ref: "#/components/parameters/XVaultSignature"
      security:
        []
      tags:
        - Webhooks
      summary: "Event: fulfillment.status.changed"
      description: Fired on every fulfillment transition — including shipped (with tracking) and delivered — so you can notify your customer without polling.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/WebhookFulfillmentStatusChanged"
      responses:
        2XX:
          description: Return any 2xx quickly to acknowledge receipt.
  fulfillment.issue.raised:
    post:
      operationId: eventFulfillmentIssueRaised
      parameters:
        - $ref: "#/components/parameters/XVaultEventID"
        - $ref: "#/components/parameters/XVaultSignature"
      security:
        []
      tags:
        - Webhooks
      summary: "Event: fulfillment.issue.raised"
      description: "Fired when a fulfillment needs attention — verification mismatch, address problem, carrier exception. The fulfillment moves to `exception` and carries the same `issue` object."
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/WebhookFulfillmentIssueRaised"
      responses:
        2XX:
          description: Return any 2xx quickly to acknowledge receipt.
  manifest.reconciliation.completed:
    post:
      operationId: eventManifestReconciliationCompleted
      parameters:
        - $ref: "#/components/parameters/XVaultEventID"
        - $ref: "#/components/parameters/XVaultSignature"
      security:
        []
      tags:
        - Webhooks
      summary: "Event: manifest.reconciliation.completed"
      description: "Fired when a manifest's reconciliation report is finalized. The payload carries the report totals; fetch the full per-line report from `GET /manifests/{manifest_id}/reconciliation`."
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/WebhookManifestReconciliationCompleted"
      responses:
        2XX:
          description: Return any 2xx quickly to acknowledge receipt.
  transfer.requested:
    post:
      operationId: eventTransferRequested
      parameters:
        - $ref: "#/components/parameters/XVaultEventID"
        - $ref: "#/components/parameters/XVaultSignature"
      security:
        []
      tags:
        - Webhooks
      summary: "Event: transfer.requested"
      description: Fired when a sell-back transfer request is created.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/WebhookTransferRequested"
      responses:
        2XX:
          description: Return any 2xx quickly to acknowledge receipt.
  transfer.completed:
    post:
      operationId: eventTransferCompleted
      parameters:
        - $ref: "#/components/parameters/XVaultEventID"
        - $ref: "#/components/parameters/XVaultSignature"
      security:
        []
      tags:
        - Webhooks
      summary: "Event: transfer.completed"
      description: Fired when a sell-back transfer completes and its items are released from custody.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/WebhookTransferCompleted"
      responses:
        2XX:
          description: Return any 2xx quickly to acknowledge receipt.
  transfer.declined:
    post:
      operationId: eventTransferDeclined
      parameters:
        - $ref: "#/components/parameters/XVaultEventID"
        - $ref: "#/components/parameters/XVaultSignature"
      security:
        []
      tags:
        - Webhooks
      summary: "Event: transfer.declined"
      description: Fired when a sell-back transfer is declined and its items return to available inventory.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/WebhookTransferDeclined"
      responses:
        2XX:
          description: Return any 2xx quickly to acknowledge receipt.
  transfer.cancelled:
    post:
      operationId: eventTransferCancelled
      parameters:
        - $ref: "#/components/parameters/XVaultEventID"
        - $ref: "#/components/parameters/XVaultSignature"
      security:
        []
      tags:
        - Webhooks
      summary: "Event: transfer.cancelled"
      description: Fired when a sell-back transfer is cancelled and its items return to available inventory.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/WebhookTransferCancelled"
      responses:
        2XX:
          description: Return any 2xx quickly to acknowledge receipt.
  fulfillment.returned:
    post:
      operationId: eventFulfillmentReturned
      parameters:
        - $ref: "#/components/parameters/XVaultEventID"
        - $ref: "#/components/parameters/XVaultSignature"
      security:
        []
      tags:
        - Webhooks
      summary: "Event: fulfillment.returned"
      description: Fired when a returned fulfillment reaches the vault and its items re-enter intake.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/WebhookFulfillmentReturned"
      responses:
        2XX:
          description: Return any 2xx quickly to acknowledge receipt.
components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
      description: "API key in format `sk_*`. Scoped to your partner account; server-side use only."
  parameters:
    Page:
      name: page
      in: query
      description: Page number (1-based).
      schema:
        type: integer
        minimum: 1
        default: 1
    PageSize:
      name: page_size
      in: query
      description: Items per page.
      schema:
        type: integer
        minimum: 1
        maximum: 250
        default: 25
    IdempotencyKey:
      description: Unique key for safe retries. Reusing a key on the same endpoint returns the original result.
      example: order-20419
      in: header
      name: Idempotency-Key
      required: true
      schema:
        maxLength: 255
        type: string
    XVaultEventID:
      description: Unique event delivery ID. Use it to deduplicate webhook deliveries.
      in: header
      name: X-Vault-Event-Id
      required: true
      schema:
        format: uuid
        type: string
    XVaultSignature:
      description: "Versioned signature in the form `v1.{unix_ts}.{hex hmac_sha256(secret, \"{unix_ts}.{raw_body}\")}`. Compute over the unmodified request body and reject invalid signatures or timestamps more than five minutes from receipt."
      in: header
      name: X-Vault-Signature
      required: true
      schema:
        type: string
    StartingAfter:
      description: Return events created after this event ID.
      in: query
      name: starting_after
      required: false
      schema:
        format: uuid
        type: string
  responses:
    BadRequest:
      content:
        application/json:
          example:
            code: validation_error
            extra:
              fields:
                qids:
                  - This field is required.
            message: Form is invalid
          schema:
            $ref: "#/components/schemas/Error"
      description: Validation failed.
      headers:
        X-Vault-Request-ID:
          $ref: "#/components/headers/XVaultRequestID"
    Conflict:
      content:
        application/json:
          example:
            code: state_conflict
            extra:
              {}
            message: The requested transition is not available from the current state.
          schema:
            $ref: "#/components/schemas/Error"
      description: The resource state prevents the requested action.
      headers:
        X-Vault-Request-ID:
          $ref: "#/components/headers/XVaultRequestID"
    NotFound:
      content:
        application/json:
          example:
            code: not_found
            extra:
              {}
            message: Not found.
          schema:
            $ref: "#/components/schemas/Error"
      description: No such resource exists for this partner and environment.
      headers:
        X-Vault-Request-ID:
          $ref: "#/components/headers/XVaultRequestID"
    TooManyRequests:
      content:
        application/json:
          example:
            code: rate_limited
            extra:
              {}
            message: Request was throttled.
          schema:
            $ref: "#/components/schemas/Error"
      description: Rate limit exceeded for this API key.
      headers:
        Retry-After:
          description: Seconds to wait before retrying.
          schema:
            type: integer
        X-RateLimit-Limit:
          description: Request ceiling for the current window.
          schema:
            type: integer
        X-RateLimit-Remaining:
          description: Requests remaining in the current window.
          schema:
            type: integer
        X-RateLimit-Reset:
          description: Unix timestamp when the window resets.
          schema:
            type: integer
        X-Vault-Request-ID:
          $ref: "#/components/headers/XVaultRequestID"
    Unauthorized:
      content:
        application/json:
          examples:
            environmentMismatch:
              value:
                code: environment_mismatch
                extra:
                  {}
                message: The API key is not valid for this environment.
            invalidApiKey:
              value:
                code: invalid_api_key
                extra:
                  {}
                message: Invalid API key.
          schema:
            $ref: "#/components/schemas/Error"
      description: "Missing, invalid, expired, or environment-mismatched API key."
      headers:
        X-Vault-Request-ID:
          $ref: "#/components/headers/XVaultRequestID"
    UnprocessableEntity:
      content:
        application/json:
          example:
            code: validation_error
            extra:
              availability:
                SEALED-BOX-ALPHA: 0
            message: Available inventory cannot satisfy the requested lines.
          schema:
            $ref: "#/components/schemas/Error"
      description: The request is valid but cannot be fulfilled from available inventory.
      headers:
        X-Vault-Request-ID:
          $ref: "#/components/headers/XVaultRequestID"
  headers:
    XVaultRequestID:
      description: Request ID for support and tracing.
      schema:
        format: uuid
        type: string
  schemas:
    QID:
      type: string
      format: uuid
      description: "Vault's canonical identifier for one physical item. It is assigned at intake and remains stable for the item's lifetime."
      example: 7c9e6679-7425-40de-944b-e07fc1f90ae7
    ItemStatus:
      type: string
      description: Custody lifecycle for one physical item. New statuses may be added compatibly within v1.
      enum:
        - received
        - digitized
        - available
        - in_fulfillment
        - in_transit
        - delivered
        - pending_transfer
        - released
        - withdrawn
      example: available
    ImageAsset:
      type: object
      description: One standardized image captured at intake. Dimensions and format are uniform and deterministic per variant — what you get is always what you expect.
      required:
        - variant
        - url
        - width
        - height
        - format
      properties:
        variant:
          type: string
          enum:
            - front
            - back
            - label
          description: Which view of the slab this asset shows.
        url:
          type: string
          format: uri
          description: Permanent CDN URL.
        width:
          type: integer
          description: Pixel width.
        height:
          type: integer
          description: Pixel height.
        format:
          type: string
          enum:
            - webp
            - jpeg
            - png
    IntakeEvent:
      type: object
      description: "One item completing digitization. Identical payload whether consumed via `GET /intakes` or the `item.intake.completed` webhook."
      required:
        - qid
        - intake_at
        - condition_flags
        - item
      properties:
        qid:
          $ref: "#/components/schemas/QID"
        intake_at:
          type: string
          format: date-time
        manifest_id:
          type: string
          format: uuid
          description: "Manifest this intake was attributed to, if any."
          nullable: true
        manifest_line_id:
          type: string
          format: uuid
          description: "Expected-manifest line this item matched, if any."
          nullable: true
        condition_flags:
          type: array
          items:
            type: string
          description: "Verification flags raised at intake (e.g. `cracked_case`, `label_mismatch`). Empty when the item verified clean."
        item:
          $ref: "#/components/schemas/Item"
    ItemSummary:
      type: object
      description: Aggregate item counts for dashboards.
      required:
        - total
        - by_status
      properties:
        total:
          type: integer
        by_status:
          type: object
          properties:
            received:
              type: integer
            digitized:
              type: integer
            available:
              type: integer
            in_fulfillment:
              type: integer
            in_transit:
              type: integer
            delivered:
              type: integer
            pending_transfer:
              type: integer
            released:
              type: integer
            withdrawn:
              type: integer
    ManifestSource:
      type: string
      description: Where this purchase batch came from.
      enum:
        - card_show
        - marketplace
        - distributor
        - other
      example: card_show
    ManifestStatus:
      type: string
      enum:
        - submitted
        - receiving
        - reconciled
      description: "`submitted` — open, nothing matched yet; `receiving` — items are arriving and matching; `reconciled` — the reconciliation report has been finalized."
    ManifestLineCreate:
      type: object
      description: One expected item. Product lines reference a pre-existing catalog SKU; graded slabs use grader and certification details.
      required:
        - description
      properties:
        external_ref:
          type: string
          description: Your identifier for this expected item; echoed on the matched item.
          nullable: true
        description:
          type: string
          description: "What you expect, in your words."
          example: 2018 Panini Prizm Luka Doncic
        grader:
          type: string
          example: PSA
          nullable: true
        cert_number:
          type: string
          description: Strongest match key when known.
          example: "48291077"
          nullable: true
        quantity:
          type: integer
          minimum: 1
          default: 1
        sku:
          description: "Catalog SKU for a product line. The SKU must already exist in this partner's catalog."
          nullable: true
          type: string
    ManifestCreate:
      type: object
      required:
        - reference
        - lines
      properties:
        reference:
          type: string
          description: "Your reference for this purchase batch (PO number, buy sheet ID)."
          example: BUY-2026-06-0031
        source:
          $ref: "#/components/schemas/ManifestSource"
        expected_arrival_start:
          type: string
          format: date
          nullable: true
        expected_arrival_end:
          type: string
          format: date
          nullable: true
        lines:
          type: array
          minItems: 1
          items:
            $ref: "#/components/schemas/ManifestLineCreate"
    ManifestLine:
      type: object
      description: An expected line and its current match status.
      required:
        - id
        - description
        - quantity
        - status
        - matched_qids
      properties:
        id:
          type: string
          format: uuid
        external_ref:
          type: string
          nullable: true
        description:
          type: string
        grader:
          type: string
          nullable: true
        cert_number:
          type: string
          nullable: true
        quantity:
          type: integer
        status:
          type: string
          enum:
            - expected
            - matched
            - missing
            - condition_mismatch
          description: "`expected` — not yet arrived; `matched` — arrived and verified; `missing` — reconciliation closed without it arriving; `condition_mismatch` — arrived but intake verification flagged it."
        matched_qids:
          type: array
          items:
            $ref: "#/components/schemas/QID"
        sku:
          nullable: true
          type: string
    Manifest:
      type: object
      required:
        - id
        - reference
        - status
        - line_count
        - created_at
      properties:
        id:
          type: string
          format: uuid
        reference:
          type: string
        source:
          $ref: "#/components/schemas/ManifestSource"
        status:
          $ref: "#/components/schemas/ManifestStatus"
        expected_arrival_start:
          type: string
          format: date
          nullable: true
        expected_arrival_end:
          type: string
          format: date
          nullable: true
        line_count:
          type: integer
        created_at:
          type: string
          format: date-time
    ManifestDetail:
      allOf:
        - $ref: "#/components/schemas/Manifest"
        - type: object
          required:
            - lines
          properties:
            lines:
              type: array
              items:
                $ref: "#/components/schemas/ManifestLine"
    ReconciliationReport:
      type: object
      description: Expected vs. received for one manifest.
      required:
        - manifest_id
        - generated_at
        - totals
        - lines
        - unexpected_items
      properties:
        manifest_id:
          type: string
          format: uuid
        generated_at:
          type: string
          format: date-time
        totals:
          type: object
          required:
            - expected
            - matched
            - missing
            - unexpected
            - condition_mismatch
          properties:
            expected:
              type: integer
            matched:
              type: integer
            missing:
              type: integer
            unexpected:
              type: integer
            condition_mismatch:
              type: integer
        lines:
          type: array
          items:
            $ref: "#/components/schemas/ManifestLine"
        unexpected_items:
          type: array
          description: Items received and attributed to this manifest that were not on it.
          items:
            type: object
            required:
              - qid
              - description
            properties:
              qid:
                $ref: "#/components/schemas/QID"
              description:
                type: string
              cert_number:
                type: string
                nullable: true
              note:
                type: string
                nullable: true
    ShippingAddress:
      type: object
      description: Where to ship. US-domestic at launch.
      required:
        - recipient_name
        - street
        - city
        - state
        - zip
        - country
      properties:
        recipient_name:
          type: string
          example: Jordan Alvarez
        street:
          type: string
          example: 480 Cedar St
        street_2:
          type: string
          example: Apt 12
          nullable: true
        city:
          type: string
          example: Austin
        state:
          type: string
          example: TX
        zip:
          type: string
          example: "78701"
        country:
          type: string
          example: US
    FulfillmentStatus:
      type: string
      description: Fulfillment lifecycle. Returned means custody has resumed and the item will repeat intake.
      enum:
        - pending
        - processing
        - shipped
        - delivered
        - cancelled
        - exception
        - returned
      example: shipped
    ProcessingStage:
      type: string
      description: Warehouse sub-stage while status is processing. It may be null when granular telemetry is unavailable; status is always authoritative.
      enum:
        - picked
        - packed
        - verified
        - labeled
        - queued_for_carrier
      nullable: true
    FulfillmentIssue:
      type: object
      description: "Present when a fulfillment is in `exception`."
      required:
        - code
        - message
        - occurred_at
      properties:
        code:
          type: string
          description: Machine-readable issue type.
          example: address_undeliverable
        message:
          type: string
        occurred_at:
          type: string
          format: date-time
    FulfillmentRequestBase:
      type: object
      properties:
        deliver_by:
          description: Advisory requested delivery date.
          format: date
          nullable: true
          type: string
        external_ref:
          description: Partner order reference echoed on the fulfillment and its events.
          nullable: true
          type: string
        special_instructions:
          description: Free text surfaced to warehouse staff during pick and pack.
          nullable: true
          type: string
    FulfillmentItemSelection:
      description: "Address inventory by QID or SKU lines, never both."
      oneOf:
        - type: object
          required:
            - qids
          not:
            required:
              - lines
          properties:
            qids:
              description: Exact physical items to fulfill.
              items:
                $ref: "#/components/schemas/QID"
              minItems: 1
              type: array
        - type: object
          required:
            - lines
          not:
            required:
              - qids
          properties:
            lines:
              description: SKU quantities to auto-allocate to available QIDs.
              items:
                $ref: "#/components/schemas/FulfillmentLine"
              minItems: 1
              type: array
    FulfillmentDestination:
      description: "Standard fulfillment uses a shipping address. Withdrawal uses the primary verified address by default or a verified address ID, and rejects free-form addresses."
      oneOf:
        - type: object
          required:
            - shipping_address
          not:
            required:
              - verified_address_id
          properties:
            shipping_address:
              $ref: "#/components/schemas/ShippingAddress"
            type:
              description: Omit to use standard fulfillment.
              enum:
                - standard
              type: string
        - type: object
          required:
            - type
          not:
            required:
              - shipping_address
          properties:
            type:
              enum:
                - withdrawal
              type: string
            verified_address_id:
              description: "Console-managed verified destination. Omit to use the organization's primary verified address."
              format: uuid
              nullable: true
              type: string
    FulfillmentRequest:
      description: A standard or withdrawal request with exactly one inventory-addressing form.
      allOf:
        - $ref: "#/components/schemas/FulfillmentRequestBase"
        - $ref: "#/components/schemas/FulfillmentItemSelection"
        - $ref: "#/components/schemas/FulfillmentDestination"
    Fulfillment:
      type: object
      description: "Everything shipping-related you would mirror to your customer. Carrier, service, and tracking populate once the label is generated."
      required:
        - id
        - type
        - qids
        - status
        - created_at
        - updated_at
      properties:
        id:
          type: string
          format: uuid
        qids:
          type: array
          items:
            $ref: "#/components/schemas/QID"
        status:
          $ref: "#/components/schemas/FulfillmentStatus"
        processing_stage:
          $ref: "#/components/schemas/ProcessingStage"
        shipping_address:
          allOf:
            - $ref: "#/components/schemas/ShippingAddress"
          nullable: true
        carrier:
          type: string
          description: Carrier code once the label exists.
          example: usps
          nullable: true
        service:
          type: string
          description: Service code once the label exists.
          example: usps_priority_mail
          nullable: true
        tracking_number:
          type: string
          example: "9400111899223857267224"
          nullable: true
        tracking_url:
          type: string
          format: uri
          nullable: true
        issue:
          allOf:
            - $ref: "#/components/schemas/FulfillmentIssue"
          nullable: true
        external_ref:
          type: string
          nullable: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        type:
          $ref: "#/components/schemas/FulfillmentType"
        special_instructions:
          nullable: true
          type: string
        deliver_by:
          format: date
          nullable: true
          type: string
        verified_address_id:
          format: uuid
          nullable: true
          type: string
    WebhookEventType:
      type: string
      enum:
        - item.intake.completed
        - item.status.changed
        - fulfillment.status.changed
        - fulfillment.issue.raised
        - manifest.reconciliation.completed
        - transfer.requested
        - transfer.completed
        - transfer.declined
        - transfer.cancelled
        - fulfillment.returned
    WebhookSubscriptionCreate:
      type: object
      required:
        - url
        - event_types
      properties:
        url:
          type: string
          format: uri
          pattern: ^https://
          description: "HTTPS endpoint to receive deliveries. Loopback, private, link-local, and cloud-metadata destinations—and redirects to them—are rejected."
        event_types:
          type: array
          minItems: 1
          items:
            $ref: "#/components/schemas/WebhookEventType"
    WebhookSubscription:
      type: object
      required:
        - id
        - url
        - event_types
        - active
        - created_at
      properties:
        id:
          type: string
          format: uuid
        url:
          type: string
          format: uri
          pattern: ^https://
        event_types:
          type: array
          items:
            $ref: "#/components/schemas/WebhookEventType"
        active:
          type: boolean
        created_at:
          type: string
          format: date-time
    WebhookSubscriptionWithSecret:
      allOf:
        - $ref: "#/components/schemas/WebhookSubscription"
        - type: object
          required:
            - secret
          properties:
            secret:
              type: string
              description: HMAC signing secret. Returned only in this response — store it securely; it cannot be retrieved again.
    WebhookEvent:
      description: "Event envelope returned by `GET /events` and `GET /events/{event_id}`."
      discriminator:
        propertyName: type
        mapping:
          item.intake.completed: "#/components/schemas/WebhookItemIntakeCompleted"
          item.status.changed: "#/components/schemas/WebhookItemStatusChanged"
          fulfillment.status.changed: "#/components/schemas/WebhookFulfillmentStatusChanged"
          fulfillment.issue.raised: "#/components/schemas/WebhookFulfillmentIssueRaised"
          manifest.reconciliation.completed: "#/components/schemas/WebhookManifestReconciliationCompleted"
          transfer.requested: "#/components/schemas/WebhookTransferRequested"
          transfer.completed: "#/components/schemas/WebhookTransferCompleted"
          transfer.declined: "#/components/schemas/WebhookTransferDeclined"
          transfer.cancelled: "#/components/schemas/WebhookTransferCancelled"
          fulfillment.returned: "#/components/schemas/WebhookFulfillmentReturned"
      oneOf:
        - $ref: "#/components/schemas/WebhookItemIntakeCompleted"
        - $ref: "#/components/schemas/WebhookItemStatusChanged"
        - $ref: "#/components/schemas/WebhookFulfillmentStatusChanged"
        - $ref: "#/components/schemas/WebhookFulfillmentIssueRaised"
        - $ref: "#/components/schemas/WebhookManifestReconciliationCompleted"
        - $ref: "#/components/schemas/WebhookTransferRequested"
        - $ref: "#/components/schemas/WebhookTransferCompleted"
        - $ref: "#/components/schemas/WebhookTransferDeclined"
        - $ref: "#/components/schemas/WebhookTransferCancelled"
        - $ref: "#/components/schemas/WebhookFulfillmentReturned"
    WebhookEventBase:
      type: object
      required:
        - id
        - created_at
      properties:
        id:
          type: string
          format: uuid
          description: Unique event ID — use for deduplication.
        created_at:
          type: string
          format: date-time
    WebhookItemIntakeCompleted:
      allOf:
        - $ref: "#/components/schemas/WebhookEventBase"
        - type: object
          required:
            - type
            - data
          properties:
            type:
              type: string
              enum:
                - item.intake.completed
            data:
              $ref: "#/components/schemas/IntakeEvent"
    WebhookItemStatusChanged:
      allOf:
        - $ref: "#/components/schemas/WebhookEventBase"
        - type: object
          required:
            - type
            - data
          properties:
            type:
              type: string
              enum:
                - item.status.changed
            data:
              $ref: "#/components/schemas/Item"
    WebhookFulfillmentStatusChanged:
      allOf:
        - $ref: "#/components/schemas/WebhookEventBase"
        - type: object
          required:
            - type
            - data
          properties:
            type:
              type: string
              enum:
                - fulfillment.status.changed
            data:
              $ref: "#/components/schemas/Fulfillment"
    WebhookFulfillmentIssueRaised:
      allOf:
        - $ref: "#/components/schemas/WebhookEventBase"
        - type: object
          required:
            - type
            - data
          properties:
            type:
              type: string
              enum:
                - fulfillment.issue.raised
            data:
              $ref: "#/components/schemas/Fulfillment"
    WebhookManifestReconciliationCompleted:
      allOf:
        - $ref: "#/components/schemas/WebhookEventBase"
        - type: object
          required:
            - type
            - data
          properties:
            type:
              type: string
              enum:
                - manifest.reconciliation.completed
            data:
              $ref: "#/components/schemas/ManifestReconciliationCompletedData"
    WebhookTransferRequested:
      allOf:
        - $ref: "#/components/schemas/WebhookEventBase"
        - type: object
          required:
            - type
            - data
          properties:
            type:
              type: string
              enum:
                - transfer.requested
            data:
              $ref: "#/components/schemas/Transfer"
    WebhookTransferCompleted:
      allOf:
        - $ref: "#/components/schemas/WebhookEventBase"
        - type: object
          required:
            - type
            - data
          properties:
            type:
              type: string
              enum:
                - transfer.completed
            data:
              $ref: "#/components/schemas/Transfer"
    WebhookTransferDeclined:
      allOf:
        - $ref: "#/components/schemas/WebhookEventBase"
        - type: object
          required:
            - type
            - data
          properties:
            type:
              type: string
              enum:
                - transfer.declined
            data:
              $ref: "#/components/schemas/Transfer"
    WebhookTransferCancelled:
      allOf:
        - $ref: "#/components/schemas/WebhookEventBase"
        - type: object
          required:
            - type
            - data
          properties:
            type:
              type: string
              enum:
                - transfer.cancelled
            data:
              $ref: "#/components/schemas/Transfer"
    WebhookFulfillmentReturned:
      allOf:
        - $ref: "#/components/schemas/WebhookEventBase"
        - type: object
          required:
            - type
            - data
          properties:
            type:
              type: string
              enum:
                - fulfillment.returned
            data:
              $ref: "#/components/schemas/Fulfillment"
    ManifestReconciliationCompletedData:
      type: object
      description: Final reconciliation totals; fetch the report endpoint for per-line detail.
      required:
        - manifest_id
        - generated_at
        - totals
      properties:
        manifest_id:
          type: string
          format: uuid
        generated_at:
          type: string
          format: date-time
        totals:
          type: object
          required:
            - expected
            - matched
            - missing
            - unexpected
            - condition_mismatch
          properties:
            expected:
              type: integer
            matched:
              type: integer
            missing:
              type: integer
            unexpected:
              type: integer
            condition_mismatch:
              type: integer
    PaginationEnvelope:
      type: object
      description: Standard list envelope shared by every paginated endpoint.
      required:
        - page_size
        - count
        - total
        - current_page_number
        - results
      properties:
        page_size:
          type: integer
          description: Requested page size.
        count:
          type: integer
          description: Items in this page.
        total:
          type: integer
          description: Total items across all pages.
        current_page_number:
          type: integer
        next_page_number:
          type: integer
          nullable: true
        next:
          type: string
          format: uri
          description: URL of the next page.
          nullable: true
        previous:
          type: string
          format: uri
          nullable: true
        previous_page_number:
          type: integer
          nullable: true
        results:
          type: array
          items:
            {}
    Error:
      description: Uniform error body for every non-success response.
      properties:
        code:
          $ref: "#/components/schemas/ErrorCode"
        extra:
          additionalProperties: true
          description: Structured details such as field errors or SKU availability.
          type: object
        message:
          description: Human-readable summary.
          type: string
      required:
        - message
        - code
        - extra
      type: object
    ErrorCode:
      description: Stable machine-readable error vocabulary for v1.
      enum:
        - validation_error
        - state_conflict
        - environment_mismatch
        - unsupported_destination
        - invalid_api_key
        - not_found
        - rate_limited
      type: string
    ItemType:
      description: Physical item representation used by this custody record.
      enum:
        - graded_slab
        - product
      type: string
    BaseItem:
      description: Fields shared by every physical item held in custody.
      properties:
        active_fulfillment_id:
          type: string
          format: uuid
          description: "The fulfillment currently holding this item, if any."
          nullable: true
        category:
          type: string
          description: Item category.
          example: sports-card
        external_ref:
          type: string
          description: "Your identifier for this item, echoed from the manifest line when matched."
          nullable: true
        image_assets:
          description: Per-item intake imagery for graded slabs or catalog imagery for product items.
          items:
            $ref: "#/components/schemas/ImageAsset"
          type: array
        intake_at:
          type: string
          format: date-time
          description: When the item completed intake and entered the system.
        manifest_line_id:
          type: string
          format: uuid
          description: "The expected-manifest line this item matched at intake, if any."
          nullable: true
        model_3d:
          description: Reserved for future render assets and always null today so clients can keep a stable schema.
          format: uri
          nullable: true
          type: string
        name:
          type: string
          description: "Display name, clean enough to show to your end customers."
          example: 2018 Panini Prizm Luka Doncic
        qid:
          $ref: "#/components/schemas/QID"
        set:
          type: string
          example: 2018-19 Panini Prizm
          nullable: true
        card_number:
          type: string
          example: "280"
          nullable: true
        sku:
          description: Warehouse SKU. Product items reference the partner catalog; graded slabs use their graded-item SKU.
          type: string
        status:
          $ref: "#/components/schemas/ItemStatus"
        type:
          $ref: "#/components/schemas/ItemType"
        updated_at:
          type: string
          format: date-time
      required:
        - qid
        - type
        - sku
        - name
        - status
        - image_assets
        - intake_at
        - updated_at
      type: object
    GradedSlabItem:
      allOf:
        - $ref: "#/components/schemas/BaseItem"
        - properties:
            cert_number:
              description: Grader certification number.
              type: string
            grade:
              type: string
            grader:
              description: Grading company.
              type: string
            type:
              enum:
                - graded_slab
              type: string
          required:
            - type
            - grader
            - grade
            - cert_number
          type: object
      description: One physical graded slab held in custody.
    ProductItem:
      allOf:
        - $ref: "#/components/schemas/BaseItem"
        - properties:
            cert_number:
              enum:
                - null
              nullable: true
              type: string
            grade:
              enum:
                - null
              nullable: true
              type: string
            grader:
              enum:
                - null
              nullable: true
              type: string
            type:
              enum:
                - product
              type: string
          required:
            - type
            - grader
            - grade
            - cert_number
          type: object
      description: One sealed catalog product unit held in custody.
    Item:
      discriminator:
        mapping:
          graded_slab: "#/components/schemas/GradedSlabItem"
          product: "#/components/schemas/ProductItem"
        propertyName: type
      oneOf:
        - $ref: "#/components/schemas/GradedSlabItem"
        - $ref: "#/components/schemas/ProductItem"
    FulfillmentType:
      enum:
        - standard
        - withdrawal
      type: string
    FulfillmentLine:
      additionalProperties: false
      properties:
        quantity:
          minimum: 1
          type: integer
        sku:
          type: string
      required:
        - sku
        - quantity
      type: object
    Dimensions:
      additionalProperties: false
      properties:
        height:
          exclusiveMinimum: true
          minimum: 0
          type: number
        length:
          exclusiveMinimum: true
          minimum: 0
          type: number
        unit:
          enum:
            - in
            - cm
          type: string
        width:
          exclusiveMinimum: true
          minimum: 0
          type: number
      required:
        - length
        - width
        - height
        - unit
      type: object
    Weight:
      additionalProperties: false
      properties:
        unit:
          enum:
            - oz
            - g
          type: string
        value:
          exclusiveMinimum: true
          minimum: 0
          type: number
      required:
        - value
        - unit
      type: object
    ProductCreate:
      properties:
        dimensions:
          $ref: "#/components/schemas/Dimensions"
        image_url:
          format: uri
          nullable: true
          type: string
        name:
          type: string
        sku:
          description: Unique within the partner organization.
          type: string
        upc:
          nullable: true
          type: string
        weight:
          $ref: "#/components/schemas/Weight"
      required:
        - name
        - sku
        - dimensions
        - weight
      type: object
    ProductUpdate:
      additionalProperties: false
      minProperties: 1
      properties:
        dimensions:
          $ref: "#/components/schemas/Dimensions"
        image_url:
          format: uri
          nullable: true
          type: string
        name:
          type: string
        sku:
          type: string
        upc:
          nullable: true
          type: string
        weight:
          $ref: "#/components/schemas/Weight"
      type: object
    Product:
      allOf:
        - $ref: "#/components/schemas/ProductCreate"
        - properties:
            created_at:
              format: date-time
              type: string
            product_id:
              format: uuid
              type: string
            updated_at:
              format: date-time
              type: string
          required:
            - product_id
            - created_at
            - updated_at
          type: object
    InventoryLevel:
      additionalProperties: false
      properties:
        allocated:
          minimum: 0
          type: integer
        available:
          minimum: 0
          type: integer
        on_hand:
          minimum: 0
          type: integer
        sku:
          type: string
      required:
        - sku
        - on_hand
        - allocated
        - available
      type: object
    CsvBatchTotals:
      additionalProperties: false
      properties:
        created:
          minimum: 0
          type: integer
        failed:
          minimum: 0
          type: integer
        submitted:
          minimum: 0
          type: integer
      required:
        - submitted
        - created
        - failed
      type: object
    CsvRowError:
      additionalProperties: false
      properties:
        code:
          $ref: "#/components/schemas/ErrorCode"
        fields:
          additionalProperties:
            items:
              type: string
            type: array
          type: object
        message:
          type: string
        row:
          description: "One-based CSV data-row number, excluding the header."
          minimum: 1
          type: integer
      required:
        - row
        - code
        - message
        - fields
      type: object
    ManifestCsvBatchResult:
      additionalProperties: false
      description: Immediate result for a manifest CSV. Valid rows are accepted into the created manifest and invalid rows are returned independently.
      properties:
        manifest:
          $ref: "#/components/schemas/Manifest"
        created:
          items:
            $ref: "#/components/schemas/ManifestLine"
          type: array
        errors:
          items:
            $ref: "#/components/schemas/CsvRowError"
          type: array
        totals:
          $ref: "#/components/schemas/CsvBatchTotals"
      required:
        - manifest
        - created
        - errors
        - totals
      type: object
    ProductCsvBatchResult:
      additionalProperties: false
      description: Immediate result for a product CSV. Valid rows are created and invalid rows are returned independently.
      properties:
        created:
          items:
            $ref: "#/components/schemas/Product"
          type: array
        errors:
          items:
            $ref: "#/components/schemas/CsvRowError"
          type: array
        totals:
          $ref: "#/components/schemas/CsvBatchTotals"
      required:
        - created
        - errors
        - totals
      type: object
    FulfillmentCsvBatchResult:
      additionalProperties: false
      description: "Immediate result for a fulfillment CSV. Valid rows create fulfillments and invalid rows are returned independently; inaccessible QIDs use the generic `not_found` row code."
      properties:
        created:
          items:
            $ref: "#/components/schemas/Fulfillment"
          type: array
        errors:
          items:
            $ref: "#/components/schemas/CsvRowError"
          type: array
        totals:
          $ref: "#/components/schemas/CsvBatchTotals"
      required:
        - created
        - errors
        - totals
      type: object
    TransferType:
      enum:
        - sell_back
      type: string
    TransferStatus:
      enum:
        - requested
        - completed
        - declined
        - cancelled
      type: string
    TransferCreate:
      additionalProperties: false
      properties:
        external_ref:
          nullable: true
          type: string
        qids:
          items:
            $ref: "#/components/schemas/QID"
          minItems: 1
          type: array
        type:
          $ref: "#/components/schemas/TransferType"
      required:
        - qids
        - type
      type: object
    Transfer:
      additionalProperties: false
      properties:
        created_at:
          format: date-time
          type: string
        external_ref:
          nullable: true
          type: string
        id:
          format: uuid
          type: string
        qids:
          items:
            $ref: "#/components/schemas/QID"
          type: array
        status:
          $ref: "#/components/schemas/TransferStatus"
        type:
          $ref: "#/components/schemas/TransferType"
        updated_at:
          format: date-time
          type: string
      required:
        - id
        - qids
        - type
        - status
        - created_at
        - updated_at
      type: object
    ApiKeyMetadata:
      additionalProperties: false
      properties:
        created_at:
          format: date-time
          type: string
        expires_at:
          format: date-time
          nullable: true
          type: string
        name:
          type: string
      required:
        - name
        - created_at
        - expires_at
      type: object
    Account:
      additionalProperties: false
      properties:
        api_key:
          $ref: "#/components/schemas/ApiKeyMetadata"
        environment:
          enum:
            - live
            - sandbox
          type: string
        organization_name:
          type: string
      required:
        - organization_name
        - environment
        - api_key
      type: object
    WebhookSubscriptionUpdate:
      additionalProperties: false
      minProperties: 1
      properties:
        active:
          type: boolean
        event_types:
          items:
            $ref: "#/components/schemas/WebhookEventType"
          minItems: 1
          type: array
        url:
          format: uri
          pattern: ^https://
          description: "HTTPS only. Loopback, private, link-local, and cloud-metadata destinations—and redirects to them—are rejected."
          type: string
      type: object
    CursorEnvelope:
      additionalProperties: false
      properties:
        has_more:
          type: boolean
        next_starting_after:
          format: uuid
          nullable: true
          type: string
        results:
          items:
            $ref: "#/components/schemas/WebhookEvent"
          type: array
      required:
        - results
        - has_more
        - next_starting_after
      type: object