Shipping
Physical redemption, as we run it on Collector Crypt Shipping API v1
Our server calls the shipping API with a server key; the user never signs in to the provider. The only thing the user signs is the redemption (the burn), with their own wallet. Our house wallet can pay the fee for them.
| Environment | Base URL |
|---|---|
| Dev | https://dev-api.collectorcrypt.com/partner/v1 |
| Prod | https://api.collectorcrypt.com/partner/v1 |
Auth: Authorization: Bearer <key>. Errors: { "statusCode": 404, "code": "CUSTOMER_UNKNOWN", "message": "…" }; we branch on code. Costs are decimal USD strings.
1. Register the user
POST /customers
{ "externalId": "<our user id>", "walletChain": "solana", "wallet": "<base58>" }
→ { "externalId": "…", "walletChain": "solana", "wallet": "…", "created": true, "createdAt": "…" }Idempotent. One externalId per wallet. GET /customers/{id} returns the same object.
2. Save an address
POST /customers/{id}/addresses
{ "fullName": "Ada Lovelace", "streetAddress": "1 Test St", "apartment": null,
"city": "Billings", "state": "MT", "zip": "59102", "country": "US",
"phoneNumber": "+14065550100", "email": "ada@example.com" }
→ { "id": "cm…", "state": "Montana", "country": "United States", … }Also GET, PATCH, DELETE /customers/{id}/addresses/{addressId}. A shipment freezes its
address at creation.
3. Estimate
POST /customers/{id}/shipments/estimate
{ "nftAddresses": ["<mint>"], "addressId": "cm…", "payCustomsDuties": false }
→ { "shippingPrice": "5.99", "insurancePrice": "0.00", "feesPrice": "0.00", "total": "5.99",
"customsDutiesEstimate": null, "breakdown": { "lines": [ ] } }4. Create the shipment
POST /customers/{id}/shipments
{ "nftAddresses": ["<mint>"], "addressId": "cm…", "payCustomsDuties": false,
"payerWallet": "<house wallet>" }
→ { "outboundShipmentId": "cm…", "reused": false, "rail": "solana",
"totalCost": "5.99", "amountDue": "5.99", "breakdown": { },
"transactions": ["<base64>"], "delistTransactions": [] }With payerWallet, the first transaction needs two signatures: the house wallet (fee) and the
user (card owner). Transactions expire in about 60–90 seconds; call rebuild, never a new
shipment. Posting the same body again returns the same shipment (reused: true).
5. Submit
POST /customers/{id}/shipments/{sid}/burn
{ "transactions": ["<base64 signed>"], "delistTransactions": [] }
→ [ { "error": null, "transactionId": "<signature>", "transactionUrl": "…" } ]A non-null error means that transaction did not land. If the wallet sent them itself, report
them with POST …/{sid}/signatures { "signatures": [ … ] } → linked / pending /
rejected. POST …/{sid}/rebuild returns fresh transactions for unburned cards without
charging the fee twice.
6. Track
GET /customers/{id}/shipments/{sid}
{ "id": "cm…", "customId": "2026100100OS123", "status": "Shipped", "nftAddresses": ["…"],
"totalCost": "5.99", "paymentRail": "solana", "paymentConfirmedAt": "…",
"trackingIds": ["1Z…"], "trackingUrls": ["https://…"], "updatedAt": "…" }List: GET /customers/{id}/shipments?status=&page=&pageSize= (≤ 100).
Statuses (a set, never backwards): Created, PaymentPending, PaymentReceived, Pending,
Processing, Shipped, Delivered, ActionRequired, Cancelled.
7. Webhook
POST to our URL on every status or tracking change:
{ "id": "<event id>", "type": "shipment.updated", "createdAt": "…",
"data": { "customerExternalId": "…", "shipment": { } } }data.shipmentis exactly the GET shape: current state, not a diff.- At least once, possibly out of order: we de-duplicate by
idand ignore anything older thanshipment.updatedAt. - Signed:
CC-Signature: t=<unix>,v1=<hex HMAC-SHA256(secret, "<t>.<raw body>")>, rejected iftis more than 5 minutes off. - Any 2xx within 10 seconds is success; failures retry for about 45 hours.
Errors we handle
| Status | Codes |
|---|---|
| 400 | VALIDATION_FAILED, CONTACT_PHONE_REQUIRED, CONTACT_EMAIL_REQUIRED, DESTINATION_RESTRICTED, WALLET_NOT_SUPPORTED, CARD_NOT_REDEEMABLE, INSUFFICIENT_BALANCE |
| 401 / 403 | UNAUTHORIZED, SCOPE_MISSING, RAIL_NOT_ENABLED, BATCH_NOT_ISSUED (call rebuild), BATCH_INCOMPLETE |
| 404 | CUSTOMER_UNKNOWN, ADDRESS_NOT_FOUND, SHIPMENT_NOT_FOUND, CARDS_NOT_FOUND |
| 409 | CUSTOMER_WALLET_CONFLICT, WALLET_ALREADY_REGISTERED, SHIPMENT_NOT_MODIFIABLE, DELIST_FAILED |
| 5xx | INTERNAL, UNAVAILABLE: retry with backoff |
What we need from you
The same shape is ideal: server-keyed, our own customer ids, the house wallet able to pay the fee, idempotent create, and a signed status webhook. Deadstock ships from Japan; tell us which countries you ship to and how duties are handled.