API Documentation
A REST API for creating shipping labels from your own software. Verify addresses, get live rates, and buy labels instantly — paid from a prepaid credit balance you fund with Bitcoin or Monero.
Sign in and create a key under Account → API keys. A verified email is required.
Fund your balance with BTC or XMR via the top-up endpoint. Credits are USD-denominated.
Purchases debit your balance and return the label in the same request — no per-order payment wait.
Authentication
Every request must include your API key as a Bearer token. Keys start with cp_live_ and are shown exactly once at creation. Keep them secret — anyone with your key can spend your credits. Revoke a compromised key immediately from your account page.
curl https://www.cryptopostage.xyz/api/v1/credits \
-H "Authorization: Bearer cp_live_YOUR_API_KEY"Request and response bodies are JSON. Errors use a consistent envelope with a stable machine-readable code and an HTTP status (400 validation, 401 auth, 402 insufficient credits, 404 not found, 422 unprocessable, 429 rate limited):
{
"error": {
"code": "INSUFFICIENT_CREDITS",
"message": "Insufficient credits: this label costs $8.99 but your balance is $2.50. Top up via POST /api/v1/credits/topup."
}
}Idempotency
POST /api/v1/purchase and POST /api/v1/credits/topup accept an optional Idempotency-Key header (any unique string up to 200 characters, e.g. a UUID). If a request times out or your connection drops, retry with the same key and body: instead of creating and charging a second order, the API returns the original result with status 200 and an Idempotency-Replayed: true header. Reusing a key with a different body returns 409. Strongly recommended for all purchase requests.
curl -X POST https://www.cryptopostage.xyz/api/v1/purchase \
-H "Authorization: Bearer cp_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-8271-attempt" \
-d '{ ...purchase body... }'Endpoints
POST /api/v1/verify-address— validate and normalize an addressPOST /api/v1/rates— get available services and pricesPOST /api/v1/purchase— buy a label with creditsGET /api/v1/orders— list your ordersGET /api/v1/orders/{order_id}— retrieve one orderGET /api/v1/credits— credit balance and recent activityPOST /api/v1/credits/topup— fund credits with crypto
Verify Address
POSThttps://www.cryptopostage.xyz/api/v1/verify-address
Checks deliverability and returns the standardized form of an address. The body is a single address object.
curl -X POST https://www.cryptopostage.xyz/api/v1/verify-address \
-H "Authorization: Bearer cp_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Jane Doe", "street1": "456 Oak Ave", "city": "Los Angeles",
"state": "CA", "zip": "90001", "country": "US"
}'Response: { valid, normalized, issues }.
Get Rates
POSThttps://www.cryptopostage.xyz/api/v1/rates
Returns available services with customer prices (postage + service fee) in USD cents. Optional carriers array (e.g. ["USPS"]) restricts results; customs is required for international shipments (see below). Weight is in ounces, dimensions in inches. For carrier flat-rate packaging, set parcel.predefinedPackage (e.g. FlatRateEnvelope) and omit dimensions — the same codes as the CSV guide.
curl -X POST https://www.cryptopostage.xyz/api/v1/rates \
-H "Authorization: Bearer cp_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"fromAddress": {
"name": "John Smith", "street1": "123 Main St", "city": "New York",
"state": "NY", "zip": "10001", "country": "US"
},
"toAddress": {
"name": "Jane Doe", "street1": "456 Oak Ave", "city": "Los Angeles",
"state": "CA", "zip": "90001", "country": "US"
},
"parcel": { "weightOz": 16, "lengthIn": 10, "widthIn": 8, "heightIn": 4 },
"mailingOriginZip": "94105"
}'{
"rates": [
{
"carrier": "USPS",
"service": "Priority",
"service_name": "USPS Priority Mail",
"amount_usd_cents": 899,
"estimated_delivery_days": 2,
"international": false
},
{
"carrier": "USPS",
"service": "GroundAdvantage",
"service_name": "USPS Ground Advantage",
"amount_usd_cents": 649,
"estimated_delivery_days": 3,
"international": false
}
]
}mailingOriginZip (optional, US domestic only): the ZIP Code where the package will physically be mailed or dropped off. Postage is calculated from this ZIP; the fromAddress stays on the label as the return address. Defaults to fromAddress.zip when omitted, so existing integrations are unchanged. Your mailing origin should reflect where the package will actually enter the carrier network — if it enters somewhere else, carriers may charge postage adjustments, delay delivery, or return the package to sender.
Note: UPS, FedEx, and DHL require a funded Adjustment Reserve on your account before purchase.
Create Purchase
POSThttps://www.cryptopostage.xyz/api/v1/purchase
Buys a label using your credit balance. Send the same shipment fields as /rates plus carrier and service (the service code from the rates response). The price is re-quoted server-side at purchase time; your balance is debited the exact live price. Returns 402 when the balance can't cover the label.
mailingOriginZip (optional, US domestic only) controls the rating origin exactly as in /rates. Supplying it constitutes your confirmation that this ZIP reflects where the package will physically be mailed. The purchased shipment echoes it back as mailing_origin_zip.
curl -X POST https://www.cryptopostage.xyz/api/v1/purchase \
-H "Authorization: Bearer cp_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"fromAddress": { ...same as rates... },
"toAddress": { ...same as rates... },
"parcel": { "weightOz": 16, "lengthIn": 10, "widthIn": 8, "heightIn": 4 },
"carrier": "USPS",
"service": "Priority",
"mailingOriginZip": "94105"
}'{
"order_id": "CP-A1B2C3D4E5",
"type": "SHIPPING",
"status": "COMPLETED",
"total_usd_cents": 899,
"credit_applied_cents": 899,
"created_at": "2026-09-02T18:00:00.000Z",
"shipments": [
{
"id": "...",
"status": "LABEL_READY",
"carrier": "USPS",
"service": "Priority",
"mailing_origin_zip": "94105",
"tracking_number": "9400100000000000000000",
"tracking_url": "https://tools.usps.com/...",
"label_url": "https://www.cryptopostage.xyz/api/labels/download?..."
}
],
"credit_balance_usd_cents": 4101
}For international shipments, include a customs declaration and phone numbers for both addresses:
"customs": {
"contentsType": "merchandise",
"signer": "John Smith",
"certify": true,
"customsItems": [
{
"description": "Cotton T-shirt",
"quantity": 2,
"valueUsdCents": 4000,
"weightOz": 12,
"hsTariffNumber": "6109.10",
"originCountry": "US"
}
]
}The label is normally generated in the same request (status: "COMPLETED", label_url set). If generation is still in progress or the order is held for review (held_for_review: true — credits stay applied; the label is generated automatically once staff clear the review), poll GET /api/v1/orders/{order_id} until the shipment reaches LABEL_READY, or use the order.completed webhook. Label download links are signed and expire; fetch a fresh one from the order endpoint any time.
List Orders
GEThttps://www.cryptopostage.xyz/api/v1/orders
Lists your orders, newest first. Query params: limit (1–100, default 20) and cursor (from next_cursor in the previous page).
Retrieve Order
GEThttps://www.cryptopostage.xyz/api/v1/orders/{order_id}
Returns one order with shipments, tracking, and fresh signed label URLs. Accepts the public order id (CP-…). For unpaid top-up orders the response includes the active payment details.
Get Credits
GEThttps://www.cryptopostage.xyz/api/v1/credits
Returns balance_usd_cents and the last 20 credit events (top-ups, purchases, adjustments).
Top Up Credits
POSThttps://www.cryptopostage.xyz/api/v1/credits/topup
Creates a crypto payment for the requested USD amount ($5–$10,000). Send the exact amount_crypto to the returned address before it expires; the balance is credited automatically once the payment confirms on-chain.
curl -X POST https://www.cryptopostage.xyz/api/v1/credits/topup \
-H "Authorization: Bearer cp_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "amount_usd_cents": 5000, "coin": "BTC" }'{
"order_id": "CP-F6G7H8I9J0",
"status": "awaiting_payment",
"amount_usd_cents": 5000,
"coin": "BTC",
"payment": {
"address": "bc1q...",
"amount_crypto": "0.00043210",
"payment_uri": "bitcoin:bc1q...?amount=0.00043210",
"expires_at": "2026-09-02T18:20:00.000Z"
}
}Webhooks
Configure a webhook endpoint on your account page to receive events instead of polling. We POST JSON to your https URL for:
order.completed— every shipment on an order has its label (fires for held-for-review orders once approved)order.failed— a label purchase failed; thedata.errorfield explains whytopup.confirmed— a credit top-up payment confirmed and your balance was credited
{
"event": "order.completed",
"created_at": "2026-09-02T18:00:05.000Z",
"data": {
"order_id": "CP-A1B2C3D4E5",
"status": "COMPLETED",
"total_usd_cents": 899,
"shipments": [
{
"status": "LABEL_READY",
"carrier": "USPS",
"service": "Priority",
"tracking_number": "9400100000000000000000",
"tracking_url": "https://tools.usps.com/go/TrackConfirmAction?tLabels=..."
}
]
}
}Every delivery is signed. Compute HMAC-SHA256 of <timestamp>.<body> with your signing secret (shown once when you save the URL) and compare it to the X-Webhook-Signature header. Reject deliveries whose timestamp is older than a few minutes to prevent replays.
// Node.js — verify a delivery
const crypto = require("crypto");
const signature = req.headers["x-webhook-signature"]; // "sha256=..."
const timestamp = req.headers["x-webhook-timestamp"];
const expected =
"sha256=" +
crypto
.createHmac("sha256", process.env.WEBHOOK_SECRET) // whsec_...
.update(`${timestamp}.${rawBody}`)
.digest("hex");
const valid = crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));Respond with a 2xx within 10 seconds. Failed deliveries are retried with exponential backoff (roughly 1 minute to 1 hour between attempts) for up to 8 attempts. Deliveries are visible on your account page. Events may occasionally arrive out of order — use created_at and the order status as the source of truth.
Rate limits & fair use
Per key: 30 rate requests/min, 20 purchases/min, 60 reads/min, 5 top-ups/min. Responses include Retry-After when limited. Need more? Contact support.
