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

> Ship cards you hold to a customer's address, charged to your balance at the carrier's full rate, plus any insurance.

Scope: `ship:cards`, granted on request · MCP tool: `create_shipment`, which takes the header below as `order_id`

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 and you get `409 PRICE_CHANGED`. Read [Shipping cards](https://catchbackcards.mintlify.site/partner-api/shipping.md) first, including what covers a parcel.

**Idempotency-Key** `string` *required*

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. Reuse it, with the same body, to retry the same shipment safely: [Retries](#retries).

**card\_ids** `string[]` *required*

1–10 card ids from [Get vault](https://catchbackcards.mintlify.site/partner-api/endpoints/get-vault.md), each once. A listed card has to be [delisted](https://catchbackcards.mintlify.site/partner-api/endpoints/delist-card.md) first.

**address** `object` *required*

Your customer's address, with the same fields and limits as [Estimate a shipment](https://catchbackcards.mintlify.site/partner-api/endpoints/estimate-shipment.md).

**address**

**name** `string` *required*

**address1** `string` *required*

**address2** `string`

**city** `string` *required*

**state** `string`

**zip** `string`

**country** `string` *required*

**phone** `string`

Required outside the US.

**rate** `object` *required*

The rate you chose from [Estimate a shipment](https://catchbackcards.mintlify.site/partner-api/endpoints/estimate-shipment.md).

**rate**

**carrier** `string` *required*

e.g. `USPS`.

**token** `string` *required*

e.g. `usps_priority`.

**max\_cost** `number` *required*

The most you'll pay for this shipment, in dollars, with any insurance: more than 0 and at most 10,000. Normally the `amount` from the estimate, or its `total` if you chose insurance.

**cover\_amount** `number`

Optional insurance: the dollars of cover you want, 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: [how insurance works](https://catchbackcards.mintlify.site/partner-api/shipping.md#insurance).

## Response

`200`, with the shipment and every field described in [List shipments](https://catchbackcards.mintlify.site/partner-api/endpoints/list-shipments.md). A new one is `processing`.

**idempotent\_replay** `boolean`

Present and `true` when this answers a retry of a shipment that went through. The body is the shipment as it stands now, which may have moved on.

## Errors

| Status | Code | When |
| - | - | - |
| `400` | | 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`, which names each bad field and never repeats its value), 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 or is 0 or negative |
| `400` | `INSUFFICIENT_BALANCE` | Your balance is below the rate, plus the premium if you chose insurance. The body has the `amount`. Nothing was charged |
| `400` | `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 |
| `400` | `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`) |
| `400` | `OVER_CUSTOMS_CAP` | An international shipment worth over \$2,500, or with a card we have no value for |
| `400` | `PARTNER_GOODS` | A partner's own goods ship from that partner |
| `403` | | The key lacks `ship:cards` or isn't linked to an account, the account needs a verified phone number (`"Phone verification required"`), the account is deleted (`"Partner account unavailable"`), or it can't request shipping |
| `403` | `FEATURE_RESTRICTED` | The account is restricted from the marketplace or from withdrawals (`capability` is `marketplace` or `withdraw`). Contact us |
| `409` | `CARDS_NOT_SHIPPABLE` | A card can't ship. `cards[]` gives each refused `card_id` and its `reason`: [what they mean](https://catchbackcards.mintlify.site/partner-api/shipping.md#which-cards-can-ship). A card lost at the last moment comes with no `cards` list: read [Get vault](https://catchbackcards.mintlify.site/partner-api/endpoints/get-vault.md) again |
| `409` | `RATE_UNAVAILABLE` | The carrier no longer offers that rate. Estimate again |
| `409` | `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`. Nothing was charged |
| `409` | `COVER_UNAVAILABLE` | No premium is quoted for cover on this service. Choose another rate, or leave `cover_amount` out. Nothing was charged |
| `409` | `IDEMPOTENCY_IN_PROGRESS` | A shipment with this id 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](https://catchbackcards.mintlify.site/partner-api/endpoints/get-shipment.md) |
| `409` | `IDEMPOTENCY_RETRY` | An earlier attempt with this id just failed, and freed the id. Resend the same id |
| `422` | `IDEMPOTENCY_KEY_REUSED` | This id was already used with a different body: a retry has to send the same cards, address, rate, `max_cost` and `cover_amount` |
| `429` | `DAILY_SHIPMENT_LIMIT_REACHED` | This key has made 50 shipments today (UTC). The body has `limit` and `resets_at` |
| `500` | | The shipment didn't go through and nothing was charged. Try again with the same id |
| `500` | `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 id can't charge twice. Don't resend it under a new id until you hear from us: [what to do](https://catchbackcards.mintlify.site/partner-api/shipping.md#unconfirmed-shipments) |
| `502` | | We couldn't confirm the carrier rate, and nothing was charged. Try again |
| `503` | | 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 id |

A refused shipment charges nothing and frees its id, so you can fix the problem and send it again under the same id.

## Retries

| You send an id that… | You get | Charged? |
| - | - | - |
| Is new | The shipment runs | Once |
| Belongs to a shipment that went through | The shipment as it stands now, plus `"idempotent_replay": true` | No, not again |
| Belongs to a shipment still running | `409 IDEMPOTENCY_IN_PROGRESS`, with `shipment_id` and `started_at` | No, not again |
| Belongs to a shipment with our team | `409 IDEMPOTENCY_IN_PROGRESS` with `needs_review: true`, until we've checked it. If it did go through, you get the shipment instead | No, not again |
| Belongs to a shipment we checked and removed, because nothing was charged | The shipment runs again | Once, if it succeeds this time |
| Belongs to an attempt that just failed, or was cut off before it reached the charge | `409 IDEMPOTENCY_RETRY`. Resend it | No |
| Belongs to an attempt we refused | The shipment runs again | Once, if it succeeds this time |
| Was used with a different body: other cards, another address, another rate, another `max_cost` or another `cover_amount` | `422 IDEMPOTENCY_KEY_REUSED`. Resend the original body, or use a new id for a new shipment | No |

A retry has to send the same body, so if a fresh estimate changes your `rate` or `max_cost`, a shipment still held under that id answers `422`. If it's with our team, wait: a new id isn't safe until you hear from us.

If a shipment still answers `409` after 30 minutes, contact us with its `shipment_id`.

```bash cURL theme={null}
curl -X POST https://catchbackcards.com/api/partner/v1/shipments \
  -H "Authorization: Bearer $CATCHBACK_KEY" \
  -H "Idempotency-Key: ship-2077" \
  -H "Content-Type: application/json" \
  -d '{
    "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
  }'
```

```json 200 theme={null}
{
  "id": "7c1e0000-…",
  "status": "processing",
  "card_ids": ["0e94dc61-5a2f-4b1e-9c3d-7f8a1b2c3d4e"],
  "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
}
```

```json 409 PRICE_CHANGED theme={null}
{ "error": "The rate is now above your max_cost.", "code": "PRICE_CHANGED", "amount": 14.1, "max_cost": 12.85 }
```

```json 409 PRICE_CHANGED with insurance theme={null}
{ "error": "The rate and its cover are now above your max_cost.", "code": "PRICE_CHANGED", "amount": 19.32, "max_cost": 18.01, "shipping_cost": 14.1, "insurance_premium": 5.22 }
```

```json 409 COVER_UNAVAILABLE theme={null}
{ "error": "Cover isn't available on this service right now. Choose another rate, or leave cover_amount out.", "code": "COVER_UNAVAILABLE" }
```

```json 429 theme={null}
{
  "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"
}
```

```json 500 SHIPMENT_OUTCOME_UNKNOWN theme={null}
{
  "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
}
```


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