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 typeLimitWindow
cu_test_ sandbox60 requestsper minute, sliding
cu_live_ live300 requestsper 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

StatusMeaningTypical cause
400Validation failedMissing field, bad pincode, weight ≤ 0
401Auth failedMissing/invalid/revoked X-API-Key
402Insufficient balanceWallet can't cover the freight — recharge
404Not foundUnknown AWB / pincode
429Rate limitedOver your per-minute quota — see retry_after_seconds
5xxOur faultRetry with backoff; report if persistent

Every error body has a human-readable error field.