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
}'
{
"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
}
{ "error": "The rate is now above your max_cost.", "code": "PRICE_CHANGED", "amount": 14.1, "max_cost": 12.85 }
{ "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 }
{ "error": "Cover isn't available on this service right now. Choose another rate, or leave cover_amount out.", "code": "COVER_UNAVAILABLE" }
{
"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"
}
{
"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
}
Shipping
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.
POST
/
api
/
partner
/
v1
/
shipments
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
}'
{
"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
}
{ "error": "The rate is now above your max_cost.", "code": "PRICE_CHANGED", "amount": 14.1, "max_cost": 12.85 }
{ "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 }
{ "error": "Cover isn't available on this service right now. Choose another rate, or leave cover_amount out.", "code": "COVER_UNAVAILABLE" }
{
"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"
}
{
"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
}
Scope:
A refused shipment charges nothing and frees its id, so you can fix the problem and send it again under the same id.
A retry has to send the same body, so if a fresh estimate changes your
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 first, including what covers a parcel.
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.object
required
object
required
The rate you chose from Estimate a shipment.
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.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.Response
200, with the shipment and every field described in List shipments. A new one is processing.
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. A card lost at the last moment comes with no cards list: read Get vault 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 |
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 |
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 |
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 |
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.
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
}'
{
"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
}
{ "error": "The rate is now above your max_cost.", "code": "PRICE_CHANGED", "amount": 14.1, "max_cost": 12.85 }
{ "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 }
{ "error": "Cover isn't available on this service right now. Choose another rate, or leave cover_amount out.", "code": "COVER_UNAVAILABLE" }
{
"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"
}
{
"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
}

