> ## Documentation Index
> Fetch the complete documentation index at: https://catchbackcards.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Buy packs

> **Scope:** `rip:packs` (needs a linked account). **Moves money.**

Opens 1–10 packs with your CatchCoins at list price. No promo codes, add-ons or bonus balance apply. Cards land in your vault, and every hit comes back with its CatchBack offer and a proof you can [check](/partner-api/verify-a-pull). A sealed product doesn't land in your vault: it isn't in Get vault, and the API can't sell it back, list it or ship it. Don't buy the Starter or a sealed mystery pack from an API-only flow.

**All or nothing:** if any pack fails to open, the whole order is refunded and none of its pulls stand.

**Refused before any charge:** a `pack_type` that List packs doesn't offer this key is a `404`; a pack with `available: false` is a `409 PACK_UNAVAILABLE`, with the `reason` List packs shows; a `risk_level` outside the pack's `risk_levels` is a `400`. A refused order charges nothing, and its order id is free to use again.

**`needs_review: true` on an error means the order may have been charged and we are settling it.** Never place it again under a new key. Follow it with [Get an order](/partner-api/endpoints/get-order). See [Idempotency](/partner-api/idempotency) and [Rate limits](/partner-api/rate-limits#pack-purchases).



## OpenAPI

````yaml /partner-api/openapi.yaml post /api/partner/v1/packs/purchase
openapi: 3.1.0
info:
  title: CatchBack Partner API
  version: 1.0.0
  summary: Open packs, trade cards, read card data and ship vaulted cards.
  description: >-
    The Partner API acts on one CatchBack account with its real CatchCoins
    balance. Several

    operations here move money or data, so read
    [Idempotency](/partner-api/idempotency) before

    you call them.


    Error bodies are JSON with a human-readable `error` and, where you might
    branch on it, a

    machine-readable `code`. Branch on the status and the `code`, because the
    `error` text can

    change. New fields can be added to any response.
  contact:
    name: CatchBack support
    email: catchbackcards@gmail.com
servers:
  - url: https://catchbackcards.com
    description: Production. Every call acts on real money and real inventory.
security:
  - bearerAuth: []
tags:
  - name: Gacha
    description: Open packs, see what they can pull, check a pull, and sell cards back.
  - name: Marketplace
    description: Search and buy cards, and manage your own listings.
  - name: Metadata
    description: Look cards up by id or cert, with price history and recent activity.
  - name: Shipping
    description: Estimate, create and track shipments of cards you hold.
  - name: Account
    description: Your key, balance, limits, vault and order history, across every rail.
  - name: Funding
    description: Deposit and withdraw your balance in USDC.
  - name: Testing
    description: Test logins for trying CatchBack without real money.
  - name: Webhooks
    description: Signed POSTs we send to your URL when something happens on your account.
paths:
  /api/partner/v1/packs/purchase:
    post:
      tags:
        - Gacha
      summary: Buy packs
      description: >-
        **Scope:** `rip:packs` (needs a linked account). **Moves money.**


        Opens 1–10 packs with your CatchCoins at list price. No promo codes,
        add-ons or bonus balance apply. Cards land in your vault, and every hit
        comes back with its CatchBack offer and a proof you can
        [check](/partner-api/verify-a-pull). A sealed product doesn't land in
        your vault: it isn't in Get vault, and the API can't sell it back, list
        it or ship it. Don't buy the Starter or a sealed mystery pack from an
        API-only flow.


        **All or nothing:** if any pack fails to open, the whole order is
        refunded and none of its pulls stand.


        **Refused before any charge:** a `pack_type` that List packs doesn't
        offer this key is a `404`; a pack with `available: false` is a `409
        PACK_UNAVAILABLE`, with the `reason` List packs shows; a `risk_level`
        outside the pack's `risk_levels` is a `400`. A refused order charges
        nothing, and its order id is free to use again.


        **`needs_review: true` on an error means the order may have been charged
        and we are settling it.** Never place it again under a new key. Follow
        it with [Get an order](/partner-api/endpoints/get-order). See
        [Idempotency](/partner-api/idempotency) and [Rate
        limits](/partner-api/rate-limits#pack-purchases).
      operationId: buyPacks
      parameters:
        - $ref: '#/components/parameters/OrderIdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - pack_type
              properties:
                pack_type:
                  type: string
                  maxLength: 100
                  description: A `pack_type` from List packs.
                category:
                  $ref: '#/components/schemas/PackCategory'
                quantity:
                  type: integer
                  minimum: 1
                  maximum: 10
                  default: 1
                  description: Up to the pack's `max_quantity`.
                risk_level:
                  $ref: '#/components/schemas/RiskLevel'
            example:
              pack_type: bronze
              category: pokemon
              quantity: 2
              risk_level: standard
      responses:
        '200':
          description: >-
            The order and every pull in it. A retry of a finished order returns
            the original body with `idempotent_replay: true`, and charges
            nothing. If we closed the order after its response was lost, the
            retry returns a shorter body with `reconciled: true` and no `pulls`,
            `summary` or `remaining_balance`: see Idempotency.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PackPurchase'
              example:
                checkout_id: c8baa66a-…
                pack_type: bronze
                quantity: 2
                total_charged: 50
                remaining_balance: 189935
                summary:
                  total_value: 32.62
                  catchback_now_total: 29.36
                  best_pull:
                    pull_id: fe04349b-…
                    name: Tyrunt
                    value: 20.62
                pulls:
                  - pull_id: fe04349b-…
                    kind: card
                    item:
                      id: 2b814ce0-…
                      name: Tyrunt
                      set: Pokemon Japanese M3-Nullifying Zero
                      category: pokemon
                      grading_company: PSA
                      grading_rank: 10
                      slab_label: 2026 Japanese M3-Nullifying Zero Tyrunt Art Rare
                      language: english
                      cert_number: '84512377'
                      market_value: 20.62
                      front_image: https://…/2b814ce0-….png
                      back_image: https://…/2b814ce0-…-back.png
                      render_image: https://…/2b814ce0-…-render.png
                      image_url: https://…/2b814ce0-…-render.png
                    value: 20.62
                    hit:
                      rarity_tier: starter
                      bucket: P0
                      bucket_odds: 0.72
                      is_chase: false
                      chase_label: null
                    catchback:
                      amount: 18.56
                      rate: 0.9
                      valid_until: '2026-09-29T20:24:55Z'
                      after:
                        amount: 16.5
                        rate: 0.8
                        valid_until: '2026-10-06T20:19:55Z'
                    proof:
                      seed: 4261688475
                      verification_hash: 9c4f1e…
                      seed_commit_hash: 2be07a…
                      timestamp: '2026-09-29T20:19:55.418Z'
                      verify_path: >-
                        /api/verify-pull?transaction_id=fe04349b-…&card_source=pokemon
        '400':
          description: >-
            A missing or malformed `Idempotency-Key`, a body that isn't JSON, a
            missing or malformed `pack_type`, a bad `category` (`goods`
            included), `quantity` or `risk_level`, a `risk_level` outside the
            pack's `risk_levels`, not enough balance, or a pack that can't be
            bought in multiples. Nothing was charged.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error: Idempotency-Key header is required
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          description: >-
            Nothing was charged. One of:


            - The key lacks `rip:packs` or isn't linked to an account.

            - `Partner account unavailable`: the account is deleted.

            - `FEATURE_RESTRICTED`: opening packs is switched off for the
            account (`capability: "open_packs"`). Contact us.


            We also answer "not linked" or "unavailable" when we couldn't read
            your key or account just then, so one retry is worth it.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RestrictedError'
              example:
                error: >-
                  Your account can't open packs right now. Contact support if
                  you think this is a mistake.
                code: FEATURE_RESTRICTED
                capability: open_packs
        '404':
          description: >-
            `pack_type` is not one List packs offers this key, including one
            this account can't open or one sent under the wrong `category`.
            Nothing was charged, and the order id is free to use again.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '409':
          description: >-
            - `PACK_UNAVAILABLE`: the pack has `available: false`. The body has
            the `reason`. Nothing was charged, and the order id is free to use
            again.

            - `IDEMPOTENCY_IN_PROGRESS`: an order with this key is still
            running, or is being settled after a failed attempt (`needs_review:
            true` says it's held for us to settle, so resending won't help until
            we do). The body usually has its `checkout_id` and `started_at`.
            Follow it with Get an order, or retry later with the same key. Don't
            retry in a tight loop.

            - `IDEMPOTENCY_RETRY`: an earlier attempt with this key failed just
            as this one arrived. Retry with the same key.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/OrderError'
                  - type: object
                    properties:
                      code:
                        type: string
                        enum:
                          - PACK_UNAVAILABLE
                          - IDEMPOTENCY_IN_PROGRESS
                          - IDEMPOTENCY_RETRY
                      reason:
                        type: string
                        description: >-
                          Only on `PACK_UNAVAILABLE`: why the pack is
                          unavailable, in plain text.
              example:
                error: A purchase with this idempotency key is still in progress.
                code: IDEMPOTENCY_IN_PROGRESS
                checkout_id: c8baa66a-…
                started_at: '2026-09-29T20:19:55.418Z'
        '422':
          description: >-
            `IDEMPOTENCY_KEY_REUSED`: this key was already used for a different
            order. Use a new key.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Error'
                  - type: object
                    properties:
                      code:
                        type: string
                        enum:
                          - IDEMPOTENCY_KEY_REUSED
              example:
                error: >-
                  This idempotency key was already used for a different
                  purchase.
                code: IDEMPOTENCY_KEY_REUSED
        '429':
          description: >-
            - `RATE_LIMITED`: too soon after your last purchase. Wait
            `retry_after_seconds`, also sent as a `Retry-After` header.

            - `DAILY_LIMIT_REACHED`: your daily pack ceiling would be exceeded.
            The body has `limit`, `used` and `resets_at`.


            A refused request charges nothing.
          headers:
            Retry-After:
              description: Seconds to wait. Sent with `RATE_LIMITED` only.
              schema:
                type: integer
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Error'
                  - type: object
                    properties:
                      code:
                        type: string
                        enum:
                          - RATE_LIMITED
                          - DAILY_LIMIT_REACHED
                      retry_after_seconds:
                        type: integer
                      limit:
                        type: integer
                      used:
                        type: integer
                      resets_at:
                        type: string
                        format: date-time
              examples:
                rate_limited:
                  summary: RATE_LIMITED
                  value:
                    error: 'Rate limited: one purchase per key every 60 seconds.'
                    code: RATE_LIMITED
                    retry_after_seconds: 43
                daily_limit:
                  summary: DAILY_LIMIT_REACHED
                  value:
                    error: 'Daily limit reached: 100 packs per day (98 used).'
                    code: DAILY_LIMIT_REACHED
                    limit: 100
                    used: 98
                    resets_at: '2026-10-29T00:00:00.000Z'
        '500':
          description: >-
            - No `code`: a pack failed to open. The whole order was refunded and
            nothing was charged.

            - `PAYMENT_OUTCOME_UNKNOWN`: we couldn't confirm whether the order
            was charged. It may have been, and nothing was delivered. It's held
            for review (`needs_review: true`) and we settle any charge. **Stop
            retrying, and never place it again under a new key:** the same key
            answers `409 IDEMPOTENCY_IN_PROGRESS` until we settle it. The body
            has its `checkout_id`: follow it with Get an order. Also sent when a
            pack failed to open and its refund errored.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/OrderError'
                  - type: object
                    properties:
                      code:
                        type: string
                        enum:
                          - PAYMENT_OUTCOME_UNKNOWN
              example:
                error: >-
                  We couldn't confirm whether this order was charged. It's held
                  for review and we'll settle any charge, so check your balance
                  before ordering again.
                code: PAYMENT_OUTCOME_UNKNOWN
                needs_review: true
                checkout_id: c8baa66a-…
        '503':
          description: >-
            We couldn't record the order, count today's packs, check the pack or
            take the payment. This attempt charged nothing, but an earlier
            attempt with the same key may have gone through, so retry shortly
            with the same key, never a new one.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error: Could not process payment. Please try again.
      security:
        - bearerAuth:
            - rip:packs
components:
  parameters:
    OrderIdempotencyKey:
      name: Idempotency-Key
      in: header
      required: true
      description: >-
        Your order id, 8–128 characters of `A-Za-z0-9_-`, unique per account
        across pack and card orders. Reuse it to retry the same order safely,
        and use a new one for every new order.
      schema:
        type: string
        pattern: ^[A-Za-z0-9_-]{8,128}$
      example: order-1042
  schemas:
    PackCategory:
      type: string
      description: A catalog that has packs and listings.
      enum:
        - pokemon
        - onepiece
        - sports
        - riftbound
        - watches
      default: pokemon
    RiskLevel:
      type: string
      enum:
        - chill
        - standard
        - aggressive
      default: standard
    PackPurchase:
      type: object
      required:
        - checkout_id
        - pack_type
        - quantity
        - total_charged
      properties:
        checkout_id:
          type: string
          description: Our id for the order. Get an order takes it.
        pack_type:
          type: string
        quantity:
          type: integer
        total_charged:
          type:
            - number
            - 'null'
        remaining_balance:
          type: number
        summary:
          type: object
          required:
            - total_value
            - catchback_now_total
            - best_pull
          properties:
            total_value:
              type: number
            catchback_now_total:
              type: number
            best_pull:
              type:
                - object
                - 'null'
              properties:
                pull_id:
                  type: string
                name:
                  type:
                    - string
                    - 'null'
                value:
                  type:
                    - number
                    - 'null'
        pulls:
          type: array
          items:
            type: object
            required:
              - pull_id
              - kind
              - item
              - value
              - hit
              - catchback
              - proof
            properties:
              pull_id:
                type: string
                description: The pull. Sell back and Verify a pull take it.
              kind:
                type: string
                enum:
                  - card
                  - sealed_pack
              item:
                description: The [card object](/partner-api/card-object), or a sealed pack.
                anyOf:
                  - $ref: '#/components/schemas/Card'
                  - $ref: '#/components/schemas/SealedPackItem'
              value:
                type:
                  - number
                  - 'null'
                description: Market value at the pull.
              hit:
                $ref: '#/components/schemas/PullHit'
              catchback:
                type:
                  - object
                  - 'null'
                description: >-
                  What CatchBack pays for it now and after the fresh window.
                  `null` for a sealed pack.
                properties:
                  amount:
                    type: number
                  rate:
                    type: number
                  valid_until:
                    type:
                      - string
                      - 'null'
                  after:
                    type:
                      - object
                      - 'null'
                    properties:
                      amount:
                        type: number
                      rate:
                        type: number
                      valid_until:
                        type:
                          - string
                          - 'null'
              proof:
                type:
                  - object
                  - 'null'
                description: >-
                  The record to [check the draw](/partner-api/verify-a-pull)
                  with. `null` for a guaranteed pull, which no roll chose.
                properties:
                  seed:
                    type: number
                  verification_hash:
                    type:
                      - string
                      - 'null'
                  seed_commit_hash:
                    type:
                      - string
                      - 'null'
                  timestamp:
                    type:
                      - string
                      - 'null'
                  verify_path:
                    type:
                      - string
                      - 'null'
                    description: >-
                      Send a `GET` to this path, with no key. `null` for a Steph
                      Curry pull.
        idempotent_replay:
          type: boolean
          description: This answers a retry of a finished order. Nothing was charged again.
        reconciled:
          type: boolean
          description: >-
            Present and `true` on the shorter body we return when we closed the
            order after its response was lost. It has `pull_ids` and a
            `message`, and no `pulls`, `summary` or `remaining_balance`.
        pull_ids:
          type: array
          items:
            type: string
          description: Present with `reconciled`. The pulls in the order
          which are in your vault.: null
        message:
          type: string
          description: Present with `reconciled`.
    Error:
      type: object
      required:
        - error
      properties:
        error:
          type: string
          description: >-
            For people. The text can change, so branch on the status and the
            `code`.
        code:
          type: string
          description: Machine-readable, where you might branch on it.
    RestrictedError:
      allOf:
        - $ref: '#/components/schemas/Error'
        - type: object
          properties:
            code:
              type: string
              enum:
                - FEATURE_RESTRICTED
            capability:
              type: string
              description: What is switched off for the account.
    OrderError:
      allOf:
        - $ref: '#/components/schemas/Error'
        - type: object
          properties:
            checkout_id:
              type: string
              description: The order. Get an order takes it.
            needs_review:
              type: boolean
              description: >-
                The order may have been charged and we are settling it. Never
                place it again under a new key.
            started_at:
              type: string
              format: date-time
              description: On `IDEMPOTENCY_IN_PROGRESS`.
            card_id:
              type: string
            price:
              type: number
              description: On `PRICE_CHANGED`, the current price.
            max_price:
              type: number
            listing_id:
              type: string
              description: On `CARD_MOVED_TO_AUCTION`.
            listing_type:
              type: string
              description: On `CARD_MOVED_TO_AUCTION`.
            refund_pending:
              type: boolean
              description: You were charged and the refund didn't go through.
    Card:
      type: object
      description: >-
        The [card object](/partner-api/card-object). Other fields appear only on
        the catalogs that have them.
      required:
        - id
        - category
        - name
        - set
        - grading_company
        - grading_rank
        - slab_label
        - language
        - market_value
        - cert_number
        - front_image
        - back_image
        - render_image
        - image_url
      properties:
        id:
          type: string
          description: The card's id.
        category:
          $ref: '#/components/schemas/CardCategory'
        name:
          type:
            - string
            - 'null'
        set:
          type:
            - string
            - 'null'
        year:
          type:
            - string
            - 'null'
        number:
          type:
            - string
            - 'null'
        variant:
          type:
            - string
            - 'null'
        rarity:
          type:
            - string
            - 'null'
          description: Printed rarity.
        card_type:
          type:
            - string
            - 'null'
        language:
          type:
            - string
            - 'null'
        slab_label:
          type:
            - string
            - 'null'
          description: The text on the grading label.
        grading_company:
          type:
            - string
            - 'null'
        grading_rank:
          type:
            - number
            - 'null'
          description: The grade.
        cert_number:
          type:
            - string
            - 'null'
          description: The grading company's certificate number.
        card_condition:
          type:
            - string
            - 'null'
        market_value:
          type: number
          description: Our current market valuation in USD. `0` when we have no valuation.
        price:
          type:
            - number
            - 'null'
          description: The shop sale price in USD.
        market_value_last_updated:
          type:
            - string
            - 'null'
        front_image:
          type:
            - string
            - 'null'
        back_image:
          type:
            - string
            - 'null'
        render_image:
          type:
            - string
            - 'null'
        image_url:
          type:
            - string
            - 'null'
          description: '`render_image`, or `front_image` when there is no render.'
        sport:
          type:
            - string
            - 'null'
        player_name:
          type:
            - string
            - 'null'
        team:
          type:
            - string
            - 'null'
        manufacturer:
          type:
            - string
            - 'null'
        parallel:
          type:
            - string
            - 'null'
        is_rookie:
          type:
            - boolean
            - 'null'
        is_autograph:
          type:
            - boolean
            - 'null'
        is_memorabilia:
          type:
            - boolean
            - 'null'
        serial_numbered:
          type:
            - string
            - 'null'
        brand:
          type:
            - string
            - 'null'
        reference_number:
          type:
            - string
            - 'null'
        video_url:
          type:
            - string
            - 'null'
        condition:
          type:
            - string
            - 'null'
        size:
          type:
            - string
            - 'null'
        description:
          type:
            - string
            - 'null'
        attributes:
          type:
            - object
            - 'null'
          description: Luxury goods only. Key-value details that vary by item.
          additionalProperties: true
    SealedPackItem:
      type: object
      title: Sealed pack
      properties:
        id:
          type:
            - string
            - integer
            - 'null'
          description: A sealed product's id is a number.
        name:
          type:
            - string
            - 'null'
        set:
          type:
            - string
            - 'null'
        category:
          type:
            - string
            - 'null'
        image_url:
          type:
            - string
            - 'null'
    PullHit:
      type: object
      description: The band, odds and chase the pull hit.
      properties:
        rarity_tier:
          type:
            - string
            - 'null'
        bucket:
          type:
            - string
            - 'null'
        bucket_odds:
          type:
            - number
            - 'null'
        is_chase:
          type: boolean
        chase_label:
          type:
            - string
            - 'null'
    CardCategory:
      type: string
      description: A card's catalog. `goods` is luxury goods and `singles` is raw singles.
      enum:
        - pokemon
        - onepiece
        - sports
        - riftbound
        - watches
        - goods
        - singles
  responses:
    Unauthorized:
      description: A missing, malformed, disabled or unknown API key.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: Missing or malformed API key
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: cb_live_<48 hex>
      description: >-
        Your API key, sent as a bearer token. Keep it on your server. See
        [Authentication](/partner-api/authentication). A request from a location
        we refuse gets a `403` with `Access denied`, whether or not it has a
        valid key: [where you can call
        from](/partner-api/errors-and-limits#where-you-can-call-from).

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.