> ## 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.

# Create a shipment

> **Scope:** `ship:cards`, opt-in, granted on request (needs a linked account). **Moves money.**

Ships cards you hold to a customer's address. It charges your balance the rate's full amount, plus the premium if you choose insurance, and queues the shipment for us to fulfill. We quote again before charging: if the total has gone above `max_cost`, nothing is charged. The account needs a verified phone number, and a key can make 50 shipments per UTC day.

Use a new `Idempotency-Key` for each shipment, and resend the same key and body to retry it: a finished shipment comes back, and is never charged twice. A refused shipment charges nothing and frees its key.

**`SHIPMENT_OUTCOME_UNKNOWN` means the charge may have gone through.** Don't resend it under a new key until you hear from us: [what to do](/partner-api/shipping#unconfirmed-shipments). Read [Shipping cards](/partner-api/shipping) first, including what covers a parcel.



## OpenAPI

````yaml /partner-api/openapi.yaml post /api/partner/v1/shipments
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 Wallet 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: support@catchbackcards.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/shipments:
    post:
      tags:
        - Shipping
      summary: Create a shipment
      description: >-
        **Scope:** `ship:cards`, opt-in, granted on request (needs a linked
        account). **Moves money.**


        Ships cards you hold to a customer's address. It charges your balance
        the rate's full amount, plus the premium if you choose insurance, and
        queues the shipment for us to fulfill. We quote again before charging:
        if the total has gone above `max_cost`, nothing is charged. The account
        needs a verified phone number, and a key can make 50 shipments per UTC
        day.


        Use a new `Idempotency-Key` for each shipment, and resend the same key
        and body to retry it: a finished shipment comes back, and is never
        charged twice. A refused shipment charges nothing and frees its key.


        **`SHIPMENT_OUTCOME_UNKNOWN` means the charge may have gone through.**
        Don't resend it under a new key until you hear from us: [what to
        do](/partner-api/shipping#unconfirmed-shipments). Read [Shipping
        cards](/partner-api/shipping) first, including what covers a parcel.
      operationId: createShipment
      parameters:
        - name: Idempotency-Key
          in: header
          required: true
          description: >-
            Your id for this shipment, 8–128 characters of `A-Za-z0-9_-`, unique
            per account among your shipments. Pack and card order ids are a
            separate set.
          schema:
            type: string
            pattern: ^[A-Za-z0-9_-]{8,128}$
          example: ship-2077
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - card_ids
                - address
                - rate
                - max_cost
              properties:
                card_ids:
                  $ref: '#/components/schemas/ShipmentCardIds'
                address:
                  $ref: '#/components/schemas/ShipmentAddress'
                rate:
                  type: object
                  description: The rate you chose from Estimate a shipment.
                  required:
                    - carrier
                    - token
                  properties:
                    carrier:
                      type: string
                      maxLength: 100
                      description: e.g. `USPS`.
                    token:
                      type: string
                      maxLength: 100
                      description: e.g. `usps_priority`.
                max_cost:
                  type: number
                  exclusiveMinimum: 0
                  maximum: 10000
                  description: >-
                    The most you'll pay for this shipment, in dollars, with any
                    insurance. Normally the `amount` from the estimate, or its
                    `total` if you chose insurance.
                cover_amount:
                  $ref: '#/components/schemas/ShipmentCoverAmount'
            example:
              card_ids:
                - 0e94dc61-5a2f-4b1e-9c3d-7f8a1b2c3d4e
              address:
                name: Sam Rivera
                address1: 1 Example Way
                city: Testville
                state: CA
                zip: '94000'
                country: US
              rate:
                carrier: USPS
                token: usps_priority
              max_cost: 12.85
      responses:
        '200':
          description: >-
            The shipment. A new one is `processing`. A retry of one that went
            through returns it as it stands now, with `idempotent_replay: true`.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Shipment'
                  - type: object
                    properties:
                      idempotent_replay:
                        type: boolean
              example:
                id: 7c1e0000-…
                status: processing
                card_ids:
                  - 0e94dc61-…
                carrier: USPS
                service: Priority Mail
                service_token: usps_priority
                cost: 12.85
                cover_amount: null
                insurance_cost: 0
                destination_country: US
                tracking_number: null
                tracking_url: null
                created_at: '2026-11-02T18:04:11.000Z'
                status_updated_at: null
                shipped_at: null
                expected_delivery: null
                delivered_at: null
        '400':
          description: >-
            - No `code`: a missing or malformed `Idempotency-Key`, a body that
            isn't JSON, `card_ids` that aren't 1–10 card ids or list one twice,
            an invalid address (`"error": "Invalid address"` with `fields`), a
            `rate` that isn't a carrier and token, a missing or out-of-range
            `max_cost`, or a `cover_amount` that isn't a dollar amount with at
            most two decimals.

            - `INSUFFICIENT_BALANCE`: your balance is below the rate, plus the
            premium if you chose insurance. The body has the `amount`. Nothing
            was charged.

            - `COVER_NOT_ALLOWED`: `cover_amount` is outside the range: from
            $100 up to the cards' value, capped at $10,000. The body has
            `order_value`, `min_cover` and `max_cover`. Nothing was charged.

            - `COUNTRY_NOT_SUPPORTED`: we don't ship to this country. A country
            code we don't recognize is a plain `400` (`Invalid address`, with
            `fields.country`).

            - `OVER_CUSTOMS_CAP`: an international shipment worth over $2,500,
            or with a card we have no value for.

            - `PARTNER_GOODS`: a partner's own goods ship from that partner.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ShipmentRequestError'
              example:
                error: Insufficient balance
                code: INSUFFICIENT_BALANCE
                amount: 12.85
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/ShipmentForbidden'
        '409':
          description: >-
            Nothing more was charged.


            - `CARDS_NOT_SHIPPABLE`: a card can't ship. `cards[]` gives each
            refused `card_id` and its `reason`: [what they
            mean](/partner-api/shipping#which-cards-can-ship). A card lost at
            the last moment comes with no `cards` list: read Get vault again.

            - `RATE_UNAVAILABLE`: the carrier no longer offers that rate.
            Estimate again.

            - `PRICE_CHANGED`: the fresh rate, plus any premium, is above
            `max_cost`. The body has `amount` and `max_cost`, and with insurance
            `shipping_cost` and `insurance_premium`.

            - `COVER_UNAVAILABLE`: no premium is quoted for cover on this
            service. Choose another rate, or leave `cover_amount` out.

            - `IDEMPOTENCY_IN_PROGRESS`: a shipment with this key is still
            running, or is with our team (`needs_review: true`). The body has
            `shipment_id` and `started_at`. Follow it with Get a shipment.

            - `IDEMPOTENCY_RETRY`: an earlier attempt with this key just failed,
            and freed the key. Resend the same key.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Error'
                  - type: object
                    properties:
                      code:
                        type: string
                        enum:
                          - CARDS_NOT_SHIPPABLE
                          - RATE_UNAVAILABLE
                          - PRICE_CHANGED
                          - COVER_UNAVAILABLE
                          - IDEMPOTENCY_IN_PROGRESS
                          - IDEMPOTENCY_RETRY
                      cards:
                        $ref: '#/components/schemas/RefusedCards'
                      amount:
                        type: number
                      max_cost:
                        type: number
                      shipping_cost:
                        type: number
                        description: On `PRICE_CHANGED` with insurance, the rate alone.
                      insurance_premium:
                        type: number
                        description: On `PRICE_CHANGED` with insurance, the premium.
                      shipment_id:
                        type: string
                      started_at:
                        type: string
                        format: date-time
                      needs_review:
                        type: boolean
              examples:
                price_changed:
                  summary: PRICE_CHANGED
                  value:
                    error: The rate is now above your max_cost.
                    code: PRICE_CHANGED
                    amount: 14.1
                    max_cost: 12.85
        '422':
          description: >-
            `IDEMPOTENCY_KEY_REUSED`: this key was already used with a different
            body. A retry has to send the same cards, address, rate, `max_cost`
            and `cover_amount`.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Error'
                  - type: object
                    properties:
                      code:
                        type: string
                        enum:
                          - IDEMPOTENCY_KEY_REUSED
        '429':
          description: >-
            `DAILY_SHIPMENT_LIMIT_REACHED`: this key has made 50 shipments today
            (UTC). The body has `limit` and `resets_at`.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Error'
                  - type: object
                    properties:
                      code:
                        type: string
                        enum:
                          - DAILY_SHIPMENT_LIMIT_REACHED
                      limit:
                        type: integer
                      resets_at:
                        type: string
                        format: date-time
              example:
                error: 'Daily shipment limit reached: 50 per key per UTC day.'
                code: DAILY_SHIPMENT_LIMIT_REACHED
                limit: 50
                resets_at: '2026-11-03T00:00:00.000Z'
        '500':
          description: >-
            - No `code`: the shipment didn't go through and nothing was charged.
            Try again with the same key.

            - `SHIPMENT_OUTCOME_UNKNOWN`: we couldn't confirm whether the charge
            went through. It's with our team (`needs_review: true`, with its
            `shipment_id`). A retry with the same key can't charge twice.
            **Don't resend it under a new key until you hear from us.**
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Error'
                  - type: object
                    properties:
                      code:
                        type: string
                        enum:
                          - SHIPMENT_OUTCOME_UNKNOWN
                      shipment_id:
                        type: string
                      needs_review:
                        type: boolean
              example:
                error: >-
                  We could not confirm whether this shipment went through. It is
                  with our team; retrying with the same Idempotency-Key is safe
                  and will not charge again.
                code: SHIPMENT_OUTCOME_UNKNOWN
                shipment_id: 7c1e0000-…
                needs_review: true
        '502':
          description: >-
            We couldn't confirm the carrier rate, and nothing was charged. Try
            again.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '503':
          description: >-
            We couldn't check the account, the cards or your daily limit, or
            start the shipment, and this attempt charged nothing. Try again with
            the same key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security:
        - bearerAuth:
            - ship:cards
components:
  schemas:
    ShipmentCardIds:
      type: array
      minItems: 1
      maxItems: 10
      uniqueItems: true
      description: >-
        Card ids from Get vault, cards you hold in the vault. A listed card has
        to be delisted first.
      items:
        type: string
        format: uuid
    ShipmentAddress:
      type: object
      description: >-
        Your customer's address. Stored only with a shipment, never in logs or
        on your account.
      required:
        - name
        - address1
        - city
        - country
      properties:
        name:
          type: string
          maxLength: 100
          description: Recipient name.
        address1:
          type: string
          maxLength: 100
        address2:
          type: string
          maxLength: 100
        city:
          type: string
          maxLength: 100
        state:
          type: string
          maxLength: 100
          description: State or region, where the country has one.
        zip:
          type: string
          maxLength: 20
          description: Postal code, where the country has one.
        country:
          type: string
          description: ISO 3166 alpha-2, e.g. `US`. Shipping countries lists where we ship.
        phone:
          type: string
          maxLength: 20
          description: Required outside the US.
    ShipmentCoverAmount:
      type: number
      minimum: 100
      maximum: 10000
      description: >-
        Optional insurance: the dollars of cover, with at most two decimals,
        from $100 up to `max_cover` from the estimate. Leave it out and the only
        cover is what the carrier includes automatically: up to $100 on USPS and
        UPS labels. Other carriers may include less or none. You carry any loss
        above your cover.
    Shipment:
      allOf:
        - $ref: '#/components/schemas/ShipmentEvent'
        - type: object
          required:
            - destination_country
          properties:
            destination_country:
              type: string
            address_removed:
              type: boolean
              description: Present and `true` once we've removed the address.
    ShipmentRequestError:
      allOf:
        - $ref: '#/components/schemas/Error'
        - type: object
          properties:
            code:
              type: string
              enum:
                - INSUFFICIENT_BALANCE
                - COVER_NOT_ALLOWED
                - COUNTRY_NOT_SUPPORTED
                - OVER_CUSTOMS_CAP
                - PARTNER_GOODS
            fields:
              type: object
              description: >-
                On an invalid address, each bad field and what's wrong with it.
                It never repeats the value.
              additionalProperties:
                type: string
            amount:
              type: number
              description: On `INSUFFICIENT_BALANCE`, the rate plus any premium.
            order_value:
              type: number
              description: On `COVER_NOT_ALLOWED`, our valuation of the cards.
            min_cover:
              type: number
              description: On `COVER_NOT_ALLOWED`, the least you can insure.
            max_cover:
              type:
                - number
                - 'null'
              description: >-
                On `COVER_NOT_ALLOWED`, the most you can insure. `null` when the
                order can't be insured.
    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.
    RefusedCards:
      type: array
      description: >-
        Each refused card and why. A card lost at the last moment comes with no
        list.
      items:
        type: object
        required:
          - card_id
          - reason
        properties:
          card_id:
            type: string
          reason:
            type: string
            enum:
              - not_in_vault
              - listed
              - shipping
              - on_hold
              - not_a_card
    ShipmentEvent:
      type: object
      description: >-
        A shipment as a webhook sends it. It has nothing from the customer's
        address.
      required:
        - id
        - status
        - card_ids
        - carrier
        - service
        - service_token
        - cost
        - cover_amount
        - insurance_cost
        - tracking_number
        - tracking_url
        - created_at
        - status_updated_at
        - shipped_at
        - expected_delivery
        - delivered_at
      properties:
        id:
          type: string
          description: The shipment's id. Get a shipment takes it.
        status:
          $ref: '#/components/schemas/ShipmentStatus'
        card_ids:
          type: array
          items:
            type: string
        carrier:
          type:
            - string
            - 'null'
          description: '`null` until we''ve confirmed a rate.'
        service:
          type:
            - string
            - 'null'
        service_token:
          type:
            - string
            - 'null'
        cost:
          type:
            - number
            - 'null'
          description: >-
            What the shipment charges in USD, including any insurance premium.
            `null` until we've confirmed a rate.
        cover_amount:
          type:
            - number
            - 'null'
          description: >-
            The insurance on this shipment, in USD. `null` without insurance,
            and before the order exists.
        insurance_cost:
          type:
            - number
            - 'null'
          description: >-
            The premium in USD, which is part of `cost`. `0` without insurance,
            and `null` before the order exists.
        tracking_number:
          type:
            - string
            - 'null'
          description: '`null` until there''s a label.'
        tracking_url:
          type:
            - string
            - 'null'
        created_at:
          type: string
        status_updated_at:
          type:
            - string
            - 'null'
          description: When the order's status last changed.
        shipped_at:
          type:
            - string
            - 'null'
        expected_delivery:
          type:
            - string
            - 'null'
        delivered_at:
          type:
            - string
            - 'null'
        needs_review:
          type: boolean
          description: Present and `true` while `status` is `needs_review`.
    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.
    ShipmentStatus:
      type: string
      description: >-
        - `pending`: the shipment hasn't reached an order yet.

        - `needs_review`: we couldn't confirm the charge, and it's with our
        team.

        - `processing`: the order is queued for fulfillment.

        - `shipped`, `delivered`: the carrier has it, or has delivered it.

        - `cancelled`: the order was cancelled or resolved outside shipping.
      enum:
        - pending
        - needs_review
        - processing
        - shipped
        - delivered
        - cancelled
  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
    ShipmentForbidden:
      description: >-
        - The key lacks `ship:cards` or isn't linked to an account.

        - `Phone verification required`.

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

        - The account can't request shipping.

        - `FEATURE_RESTRICTED`: the account is restricted from the marketplace
        or from withdrawals (`capability` is `marketplace` or `withdraw`).
        Contact us.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/RestrictedError'
  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.