Courier Uncle Public API v1
Book shipments, compare courier rates, check serviceability and track parcels from your own systems. Every API booking is priced and validated by exactly the same engine as the dashboard.
Base URL: https://merchant.courieruncle.com/api/ext/v1
Authentication
Generate keys in Dashboard → Settings → API Keys. Send the key on every request in the X-API-Key header. Keys are shown once at creation — store them like passwords.
Sandbox — cu_test_…
Full validation and real pricing. Bookings are simulated: nothing persists, wallet untouched. Build and test safely.
Live — cu_live_…
Real bookings: sequential AWBs, wallet debits, courier pickups. Use only from your server, never in browser code.
curl https://merchant.courieruncle.com/api/ext/v1/ \ -H "X-API-Key: cu_test_XXXXXXXXXXXXXXXX"
Rate limits
| Key type | Limit | Window |
|---|---|---|
cu_test_ sandbox | 60 requests | per minute, sliding |
cu_live_ live | 300 requests | per minute, sliding |
Every response carries your live quota:
X-RateLimit-Limit: 300 X-RateLimit-Remaining: 287 X-RateLimit-Reset: 1789456123 # unix time the window frees up
Exceeding the limit returns 429:
{
"error": "Rate limit exceeded",
"retry_after_seconds": 21,
"docs": "https://courieruncle.com/api-docs#rate-limits"
}Back off using Retry-After/retry_after_seconds. Need a higher live limit? Contact us with your volume.
Endpoints
GET/
Index — verifies your key, returns mode (sandbox/live) and the endpoint map.
GET/pincode/{pincode}/
City, state and postal-circle details for a 6-digit pincode.
GET https://merchant.courieruncle.com/api/ext/v1/pincode/110001/
{ "pincode": "110001", "city": "New Delhi", "state": "Delhi" }GET/serviceability/?origin=&destination=
Which couriers serve a route, estimated days, COD availability.
GET https://merchant.courieruncle.com/api/ext/v1/serviceability/?origin=110001&destination=400001
{
"origin": "110001", "destination": "400001", "serviceable": true,
"couriers": [
{ "courier_code": "delhivery", "courier_name": "Delhivery",
"estimated_days": 3, "is_cod_available": true }
]
}POST/rates/
Compare rates across all couriers — zone, chargeable weight (volumetric aware) and per-courier pricing with a recommendation.
POST https://merchant.courieruncle.com/api/ext/v1/rates/
{
"origin_pincode": "110001",
"destination_pincode": "400001",
"weight_kg": 0.5,
"payment_mode": "COD", // PREPAID | COD
"length_cm": 20, "width_cm": 15, "height_cm": 10 // optional
}
{
"zone": "C", "zone_name": "Metro to Metro",
"chargeable_weight_kg": 0.6,
"couriers": [
{ "courier_code": "delhivery", "courier_name": "Delhivery",
"rate": 74.5, "base_rate": 47.0, "cod_charge": 27.5,
"estimated_days": 3, "zone": "C", "recommended": true }
]
}POST/shipments/
Book a shipment. Sandbox keys simulate the booking (validated + priced, nothing persisted); live keys create the AWB and debit the wallet.
POST https://merchant.courieruncle.com/api/ext/v1/shipments/
{
"pickup_contact_name": "Store Ops",
"pickup_contact_phone": "9876543210",
"pickup_address": "Warehouse 4, Udyog Vihar",
"pickup_city": "Gurgaon", "pickup_pincode": "122001",
"recipient_name": "Asha Verma",
"recipient_phone": "9812345678",
"delivery_address": "12 MG Road",
"delivery_city": "Mumbai", "delivery_state": "Maharashtra",
"delivery_pincode": "400001",
"weight_kg": 0.5, "payment_mode": "COD", "cod_amount": 1299,
"courier_code": "delhivery" // optional — omit to auto-pick per your courier rules
}
201 {
"mode": "live",
"shipment": {
"tracking_id": "CU0000000042", "status": "CREATED",
"courier": "Delhivery", "rate": 74.5,
"label_url": "/api/ext/v1/shipments/CU0000000042/label/",
"tracking_link": "https://courieruncle.com/track?id=CU0000000042"
}
}GET/shipments/{awb}/
Shipment detail + full tracking timeline (newest first).
POST/shipments/{awb}/cancel/
Cancel before pickup — freight refunds to the wallet. Terminal statuses can't be cancelled.
POST https://merchant.courieruncle.com/api/ext/v1/shipments/CU0000000042/cancel/
{ "reason": "Customer changed mind" }Webhooks
Register endpoints in Dashboard → Settings → Webhooks and we push every status change to you in real time — no polling. Pick the events per endpoint:
shipment.created shipment.picked_up shipment.in_transit shipment.out_for_delivery shipment.delivered shipment.ndr shipment.rto shipment.cancelled cod.remitted weight.dispute_raised
Delivery payload
POST https://yourstore.com/webhooks/courieruncle
Content-Type: application/json
X-CU-Signature: sha256=8f2a1c… # HMAC of the raw body with your webhook secret
{
"event": "shipment.out_for_delivery",
"order_id": "9876543210",
"awb_number": "CU0000000042",
"carrier": "Delhivery",
"current_status": "OUT_FOR_DELIVERY",
"status_time": "2026-07-19 14:22:31",
"delivery_name": "Asha Verma",
"delivery_city": "Mumbai",
"delivery_pincode": "400001",
"payment_mode": "COD",
"cod_amount": 1299.0,
"tracking_link": "https://courieruncle.com/track?id=CU0000000042"
}Verify the signature (always!)
# Python
import hmac, hashlib
expected = 'sha256=' + hmac.new(
WEBHOOK_SECRET.encode(), raw_body, hashlib.sha256).hexdigest()
valid = hmac.compare_digest(expected, request.headers['X-CU-Signature'])- Respond 2xx within 5 seconds — do heavy work async on your side.
- Failed deliveries are retried once immediately; design your handler to be idempotent on
awb_number + current_status. - The last delivery result per endpoint is visible in Settings → Webhooks.
Errors
| Status | Meaning | Typical cause |
|---|---|---|
| 400 | Validation failed | Missing field, bad pincode, weight ≤ 0 |
| 401 | Auth failed | Missing/invalid/revoked X-API-Key |
| 402 | Insufficient balance | Wallet can't cover the freight — recharge |
| 404 | Not found | Unknown AWB / pincode |
| 429 | Rate limited | Over your per-minute quota — see retry_after_seconds |
| 5xx | Our fault | Retry with backoff; report if persistent |
Every error body has a human-readable error field.
