Downloads
Import the Postman collection, set the api_key variable to your key, and every request is ready to run. The OpenAPI 3.1 spec (/openapi.json) is the machine-readable source of truth — import it into Postman/Insomnia/Swagger UI or generate a client SDK from it.
Overview
The Skyfleet API is a JSON REST API. All endpoints are relative to the base URL below and require authentication (except the public tracking endpoint). Use it to check rates, create and book shipments, track parcels, reconcile COD and wallet, handle NDR/returns, and receive real-time webhook events.
Base URL: https://api.skyfleetnow.com/api/v1
Content-Type: application/jsonAll requests and responses are UTF-8 JSON. Timestamps are ISO-8601 (UTC). Monetary values are in INR (₹) unless a field name ends in Paise. Weights are in grams, dimensions in centimetres.
Authentication
Authenticate every request with an API key. Generate one in the dashboard under Settings → Developer → API Keys. The full key (sf_live_…) is shown once at creation — store it securely; you can revoke and re-create at any time. A key is scoped to your organization.
Send it in the X-API-Key header:
curl https://api.skyfleetnow.com/api/v1/wallet \
-H "X-API-Key: sf_live_xxxxxxxxxxxxxxxxxxxxxxxx"Keep keys server-side only — never ship them in a browser, mobile app, or public repo. Rotate immediately if exposed (revoke the old key, create a new one).
Troubleshooting: {"message":"Access token required"}
The same key works on every endpoint — there is no separate token. This error means the X-API-Key header didn't reach the request. Check, in order:
- Use HTTPS, not HTTP.
http://api.skyfleetnow.comreturns a 301 redirect tohttps://, and most HTTP clients drop custom headers on redirect — so the key is lost. Always callhttps://api.skyfleetnow.comdirectly. - No trailing slash. Use
/channels/orders, not/channels/orders/. - Send the header on every request. If it's set on one call but not another, set it as a default header on your HTTP client so all requests include it.
- Exact header name:
X-API-Key(notX_API_KeyorX-Api_Key).
Other messages: "Invalid, revoked, or expired API key" = the key was received but doesn't match (regenerate it); 401 with a key = same.
Responses & errors
Every response uses a consistent envelope.
Success
{ "success": true, "data": { ... }, "message": "optional" }Paginated list
{
"success": true,
"data": [ ... ],
"meta": { "total": 120, "page": 1, "limit": 20, "totalPages": 6 }
}List endpoints accept page (default 1) and limit (default 20).
Error
{ "success": false, "message": "Human-readable reason", "errors": { "field": ["..."] } }| Status | Meaning |
|---|---|
400 | Bad request / validation failed |
401 | Missing / invalid / revoked API key |
402 | Wallet not configured (booking) |
403 | Forbidden — e.g. KYC not approved |
404 | Not found |
409 | Duplicate order reference (DUPLICATE_ORDER_REF) |
Enums
- ShipmentStatus:
CREATED, LABEL_GENERATED, PICKUP_SCHEDULED, PICKUP_FAILED, PICKED_UP, IN_TRANSIT, OUT_FOR_DELIVERY, DELIVERED, DELIVERY_FAILED, RTO_INITIATED, RTO_IN_TRANSIT, RTO_DELIVERED, CANCELLED, LOST, DAMAGED - PaymentMode:
PREPAID, COD - ShipmentType:
FORWARD, REVERSE, EXCHANGE - NdrAction:
REATTEMPT, RTO, CHANGE_ADDRESS, CONTACT_CUSTOMER
Booking prerequisites (KYC + wallet)
POST /shipments creates a draft (status CREATED, no AWB, no charge). Booking a draft (assigning a courier + AWB) is gated:
- Your organization's KYC must be APPROVED (else
403). - A wallet must be configured (else
402); prepaid wallets must cover the freight.
Complete KYC and top up your wallet in the dashboard before booking via API.
Rates & serviceability
https://api.skyfleetnow.com/api/v1/rates/calculatecurl https://api.skyfleetnow.com/api/v1/rates/calculate -X POST \
-H "X-API-Key: sf_live_…" -H "Content-Type: application/json" \
-d '{
"pickupPincode": "110011",
"dropPincode": "400001",
"weightGrams": 500,
"paymentMode": "PREPAID",
"lengthCm": 20, "breadthCm": 15, "heightCm": 10,
"invoiceValue": 1499
}'
// 200 → data: [
{
"courier": "Delhivery Surface", "carrierId": "delhivery-surface",
"zone": "METRO", "chargeableWeightGrams": 500,
"baseRate": 46.61, "codCharge": 0, "gstAmount": 12.05,
"totalCharge": 78.95, "eddDays": 3, "isAvailable": true
}
]https://api.skyfleetnow.com/api/v1/rates/serviceability/detailed?pickup=110011&drop=400001&serviceType=FORWARDhttps://api.skyfleetnow.com/api/v1/rates/classify?pickup=110011&drop=400001Shipments
https://api.skyfleetnow.com/api/v1/shipmentscurl https://api.skyfleetnow.com/api/v1/shipments -X POST \
-H "X-API-Key: sf_live_…" -H "Content-Type: application/json" \
-d '{
"shipmentType": "FORWARD",
"paymentMode": "COD",
"orderReferenceNumber": "ORD-1042",
"invoiceValue": 1499,
"codAmount": 1499,
"pickup": {
"name": "Acme Warehouse", "phone": "9876543210",
"address": "Plot 4, Industrial Area", "city": "New Delhi",
"state": "Delhi", "pincode": "110011"
},
"drop": {
"name": "Ravi Kumar", "phone": "9812345678",
"address": "12 MG Road", "city": "Mumbai",
"state": "Maharashtra", "pincode": "400001"
},
"packages": [{
"weightGrams": 500, "lengthCm": 20, "breadthCm": 15, "heightCm": 10,
"items": [{ "name": "Cotton T-Shirt", "sku": "TS-01", "quantity": 1, "unitPrice": 1499 }]
}]
}'
// 201 → data: { "shipment": { "id": "...", "status": "CREATED", ... } }Required: shipmentType, paymentMode, invoiceValue, pickup, drop, packages (≥1, each with weight + dimensions + items). codAmount is required for COD. orderReferenceNumber must be unique among live orders — a duplicate returns 409 unless you pass "allowDuplicate": true. You can reference a saved pickup with pickupAddressId instead of an inline pickup object.
https://api.skyfleetnow.com/api/v1/shipments/:id/bookcurl https://api.skyfleetnow.com/api/v1/shipments/SHIP_ID/book -X POST \
-H "X-API-Key: sf_live_…" -H "Content-Type: application/json" \
-d '{ "carrierId": "bluedart-air" }' // exact rate code (recommended)
// — OR — -d '{ "carrierBrand": "DELHIVERY" }' // courier brand
// — OR — omit the body entirely to auto-allocate
// 200 → data: { "id": "...", "awbNumber": "...", "carrierId": "bluedart-air", "status": "LABEL_GENERATED",
// "labelUrl": "https://api.skyfleetnow.com/api/v1/shipments/SHIP_ID/label.pdf", ... }
// labelUrl is a Skyfleet-branded label link — fetch it with your X-API-Key header (follows a redirect to the PDF).Both fields are accepted (this resolves the earlier docs-vs-Postman ambiguity):
carrierId— the exact rate code incl. service (e.g.bluedart-air/bluedart-surface). Recommended — take it from/rates/calculate, which preserves the Air vs Surface pick.carrierBrand— the courier brand (e.g.DELHIVERY,BLUEDART_AIR). Uppercase provider-account form.
Send either one. If both are present, carrierBrand selects the brand and carrierId refines the service. Omit both to auto-allocate by your allocation rules.
All carrierId values
| Courier | Air | Surface |
|---|---|---|
| Blue Dart | bluedart-air | bluedart-surface |
| Delhivery | delhivery-air | delhivery-surface |
| DTDC | dtdc-air | dtdc-surface |
| Xpressbees | xpressbees-air | xpressbees-surface |
| Ekart | — | ekart-surface |
| Amazon | — | amazon-surface |
| Professional | — | professional-surface |
| Shadowfax | — | shadowfax-surface |
Uppercase forms (BLUEDART_AIR, …) also work. If a carrierId isn't serviceable/priced for that lane + merchant, the booking returns a clear error rather than silently switching couriers.
https://api.skyfleetnow.com/api/v1/shipments?status=IN_TRANSIT&page=1&limit=20status, statusGroup, paymentMode, carrier, search, from, to, awb, orderId, hasAwb, unprocessed.status takes a single ShipmentStatus (see Enums). For broad buckets use statusGroup:
| statusGroup | Includes |
|---|---|
pickups | Awaiting/scheduled pickup |
inTransit | Picked up → out for delivery |
deliveryFailed | Failed delivery attempts (NDR) |
delivered | Delivered |
rto | RTO initiated / in transit / delivered |
Also: hasAwb=yes|no (booked vs draft), unprocessed=yes (drafts needing action).
statusGroup only groups results for filtering — it never collapses the data. Every shipment always returns its exact status (e.g. OUT_FOR_DELIVERY, RTO_IN_TRANSIT). To filter to one precise state use ?status=OUT_FOR_DELIVERY, and there's a dedicated shipment.out_for_delivery webhook. You never lose granularity.https://api.skyfleetnow.com/api/v1/shipments/:idhttps://api.skyfleetnow.com/api/v1/shipments/:idCREATED, no AWB).https://api.skyfleetnow.com/api/v1/shipments/:id/cancel{ "reason": "..." } (optional).https://api.skyfleetnow.com/api/v1/shipments/:id/label.pdf labelUrl in booking / shipment responses.https://api.skyfleetnow.com/api/v1/shipments/:id/label-url{ url, ours }).https://api.skyfleetnow.com/api/v1/shipments/:id/invoice-urlhttps://api.skyfleetnow.com/api/v1/shipments/:id/pod { recipientName, recipientRelation, recipientContact, documentUrl, documentUrls, fetchedAt }. documentUrl is a downloadable signed-POD document (open or download directly). Where the courier exposes POD on demand, the first call fetches and caches it, so allow a moment for the initial request.labelUrl in booking and shipment responses points at /shipments/:id/label.pdf — fetch it with your X-API-Key header and follow the redirect (any standard HTTP client does this automatically). It always serves the current Skyfleet label, so don't cache the redirected URL — re-fetch the endpoint when you need the file. (Amazon-carrier shipments return Amazon's own label.)https://api.skyfleetnow.com/api/v1/shipments/bulk-download{ "shipmentIds": [...], "type": "labels" | "invoices" }.Sales Channel orders
Orders synced from a connected store (Shopify, Amazon, WooCommerce, etc.) are a separate resource from shipments. They live under /channels/orders. A channel order only appears under /shipments after it's imported — so if you're looking for freshly-synced store orders, use the endpoints here, not /shipments.
https://api.skyfleetnow.com/api/v1/channels/orders?status=NEW&page=1&limit=20status, integrationId, errorType.curl "https://api.skyfleetnow.com/api/v1/channels/orders?status=NEW" -H "X-API-Key: sf_live_…"
// 200 → data: [
{
"id": "co_abc", "externalOrderId": "5521", "externalOrderNumber": "#1042",
"customerName": "Ravi Kumar", "destinationCity": "Mumbai", "destinationPincode": "400001",
"paymentMode": "COD", "totalValue": 1499, "status": "NEW",
"integration": { "name": "My Shopify Store", "type": "SHOPIFY" },
"shipmentId": null, "shipmentStatus": null, "shipmentAwb": null
}
], "meta": { "total": 12, "page": 1, "limit": 20, "totalPages": 1 }status values: NEW (synced, not yet imported), IMPORTED, SKIPPED, FAILED, CANCELLED_ON_CHANNEL.
https://api.skyfleetnow.com/api/v1/channelshttps://api.skyfleetnow.com/api/v1/channels/orders/statshttps://api.skyfleetnow.com/api/v1/channels/orders/:id/bookcurl "https://api.skyfleetnow.com/api/v1/channels/orders/co_abc/book" -X POST \
-H "X-API-Key: sf_live_…" -H "Content-Type: application/json" \
-d '{ "carrierId": "bluedart-air" }' // optional; omit to auto-allocate
// 200 → data: { "channelOrderId":"co_abc", "shipmentId":"ship_x", "awbNumber":"77812345678", "shipment": { ... } }https://api.skyfleetnow.com/api/v1/channels/orders/:id/importhttps://api.skyfleetnow.com/api/v1/channels/orders/bulk-import{ "orderIds": [...], "mode": "draft" | "allocate" | "book" }.https://api.skyfleetnow.com/api/v1/channels/orders/:id/skipTypical flow: pull store orders → ship
GET /channels/orders?status=NEW → un-imported synced orders
POST /channels/orders/{id}/import → creates a draft shipment (returns shipmentId)
POST /shipments/{id}/book → book it (returns AWB)
// One-shot alternative:
POST /channels/orders/bulk-import { "orderIds": ["co_abc"], "mode": "book" }Orders sync but don't auto-import? Turn on auto-create
By default, synced orders sit as NEW in the channel-order feed until you import them. To have Skyfleet auto-create a draft shipment for every incoming order, enable autoCreateShipments on the integration (and set a default pickup, since a shipment needs one):
https://api.skyfleetnow.com/api/v1/channels/:idcurl "https://api.skyfleetnow.com/api/v1/channels/INTEGRATION_ID" -X PATCH \
-H "X-API-Key: sf_live_…" -H "Content-Type: application/json" \
-d '{
"autoCreateShipments": true,
"defaultPickupAddressId": "addr_123", // required for auto-create
"defaultPaymentMode": "COD", // optional fallback
"defaultWeightGrams": 500, // optional fallback
"initialSyncDays": 30
}'Get integration IDs from GET /channels, and pickup-address IDs from GET /shipments/pickup-addresses. With autoCreateShipments off, orders stay in the feed and you import them yourself (above). Either way, auto-create produces drafts — call /shipments/:id/book to book, or use bulk-import with mode:"book".
/channels/orders = raw orders from the store · /shipments = orders in Skyfleet's shipping pipeline (drafts + booked). Import bridges the two.Tracking
https://api.skyfleetnow.com/api/v1/shipments/:awb/track{ shipment, tracking } with the live courier timeline.https://api.skyfleetnow.com/api/v1/public/track/:awbcurl https://api.skyfleetnow.com/api/v1/public/track/DL1234567890
// 200 → data: {
"awbNumber": "DL1234567890", "status": "OUT_FOR_DELIVERY",
"courier": "Delhivery", "customerName": "Ravi K.",
"timeline": { "orderedAt": "...", "pickedUpAt": "...", "deliveredAt": null, "expectedDeliveryDate": "..." },
"events": [ { "status": "OUT_FOR_DELIVERY", "description": "...", "location": "Mumbai", "eventTime": "..." } ]
}For most integrations, prefer webhooks over polling — you get pushed a status event the moment it changes.
COD
https://api.skyfleetnow.com/api/v1/cod/remittances?status=PROCESSING&page=1https://api.skyfleetnow.com/api/v1/cod/remittance-schedulehttps://api.skyfleetnow.com/api/v1/cod/eligiblehttps://api.skyfleetnow.com/api/v1/cod/upcominghttps://api.skyfleetnow.com/api/v1/cod/transactions?from=2026-07-01&to=2026-07-31/cod/transactions.csv.Wallet
https://api.skyfleetnow.com/api/v1/wallethttps://api.skyfleetnow.com/api/v1/wallet/confighttps://api.skyfleetnow.com/api/v1/wallet/transactions?category=orders&page=1category: recharges | orders. CSV at /wallet/transactions.csv.NDR (failed deliveries)
https://api.skyfleetnow.com/api/v1/ndr?status=PENDING&page=1/ndr/stats.Filters: status, reason, page, limit, plus awb and orderReferenceNumber(substring match) to fetch the NDR event(s) for a specific shipment — e.g. /ndr?orderReferenceNumber=ORD-1042 or /ndr?awb=DL1234567890.
https://api.skyfleetnow.com/api/v1/ndr/actioncurl https://api.skyfleetnow.com/api/v1/ndr/action -X POST \
-H "X-API-Key: sf_live_…" -H "Content-Type: application/json" \
-d '{
"ndrEventId": "NDR_ID",
"action": "REATTEMPT", // or RTO | CHANGE_ADDRESS | CONTACT_CUSTOMER
"preferredDate": "2026-07-12",
"notes": "Customer will be home after 5pm"
}'For action: "CHANGE_ADDRESS", also send the corrected consignee details:
-d '{
"ndrEventId": "NDR_ID",
"action": "CHANGE_ADDRESS",
"newAddress": "12 MG Road, Indiranagar", // REQUIRED
"newPincode": "560038", // REQUIRED (6-digit)
"newCity": "Bengaluru", // optional
"newState": "Karnataka", // optional
"newPhone": "9876543210" // optional — corrected contact
}'
// To change ONLY the phone: resend the current address in newAddress/newPincode with the new newPhone.https://api.skyfleetnow.com/api/v1/ndr/bulk-action{ "ndrEventIds": [...], "action": "REATTEMPT", "notes": "..." }.Returns (Reverse pickup)
https://api.skyfleetnow.com/api/v1/rvp/from-forwardcurl https://api.skyfleetnow.com/api/v1/rvp/from-forward -X POST \
-H "X-API-Key: sf_live_…" -H "Content-Type: application/json" \
-d '{
"forwardAwb": "DL1234567890", // or "forwardShipmentId"
"orderReferenceNumber": "RET-1042", // optional — YOUR ref, echoed in webhooks
"isQcRequired": false, // default (defaults to "<forward-ref>-RVP")
"reasonForReturn": "Size mismatch"
}'
// → returns a REVERSE shipment draft; book it to schedule the pickup.The addresses are swapped automatically (customer → pickup, your warehouse → drop). Pass orderReferenceNumber to set your own reference — it's echoed back on every webhook for the reverse shipment, exactly like forward orders.
https://api.skyfleetnow.com/api/v1/rvp/createcurl https://api.skyfleetnow.com/api/v1/rvp/create -X POST \
-H "X-API-Key: sf_live_…" -H "Content-Type: application/json" \
-d '{
"orderReferenceNumber": "RET-9001", // optional — YOUR ref, echoed in webhooks
"pickup": { // where the courier collects (the customer)
"name": "Asha R", "phone": "9876543210",
"address": "12 MG Road", "city": "Bengaluru", "state": "Karnataka", "pincode": "560038"
},
"drop": { // where it returns to (your warehouse)
"name": "Acme Returns", "phone": "9000000000",
"address": "Plot 4, Sector 8", "city": "Gurugram", "state": "Haryana", "pincode": "122001"
},
"items": [{ "itemName": "Blue Kurta", "sku": "KUR-BLU-M", "quantity": 1, "unitPrice": 899 }],
"isQcRequired": false,
"weightKg": 0.5 // optional (default 0.5); dimensionsCm optional
}'
// → returns a REVERSE shipment draft; book it with POST /shipments/:id/book.https://api.skyfleetnow.com/api/v1/rvp?status=PICKED_UP&page=1https://api.skyfleetnow.com/api/v1/rvp/stats{ total, qcRequired, byStatus }.Doorstep QC (quality check)
Set isQcRequired: true on either create call to have the pickup agent inspect the item at the doorstep before accepting it. To give the agent something to check against, pass each item's imageUrl — it's forwarded to the courier as the reference photo:
"items": [
{
"itemName": "Blue Kurta", "sku": "KUR-BLU-M",
"quantity": 1, "unitPrice": 899,
"imageUrl": "https://cdn.yourstore.com/kur-blu-m.jpg", // QC reference photo
"color": "Blue", "brand": "Acme", "hsnCode": "6104" // all optional
}
]When the item fails QC, the reverse shipment carries the result — readable on GET /shipments/:id and pushed on shipment.delivery_failed:
{
"qcStatus": "fail", // "passed" | "fail" | "fail_hub" | null
"qcRemarks": "Brand tag missing; colour mismatch",
"qcImages": ["https://…/qc-1.jpg", "https://…/qc-2.jpg"], // photos taken at the door
"qcCheckedAt": "2026-08-18T10:22:05.000Z"
}Booking a reverse shipment (POST /shipments/:id/book) rate-shops only the reverse (RVP) courier lane — forward couriers are never offered for a return.
Webhooks (real-time events)
Instead of polling, subscribe to events and Skyfleet will POST them to your URL the moment they happen. Create and manage endpoints in the dashboard under Settings → Webhooks (set a URL, pick events, get a signing secret).
Events
shipment.created shipment.out_for_delivery
shipment.label_generated shipment.delivered
shipment.pickup_scheduled shipment.delivery_failed
shipment.picked_up shipment.rto_initiated
shipment.in_transit shipment.rto_in_transit
shipment.cancelled shipment.rto_delivered
shipment.lost shipment.damagedOne event per shipment status. Every payload echoes your orderReferenceNumber (and awbNumber), so you can reconcile against your own order — including reverse shipments. Note: shipment.created is subscribable but not currently emitted (a shipment first surfaces on shipment.label_generated, i.e. once it's booked).
Payload
POST <your-url>
Headers:
X-Skyfleet-Event: shipment.delivered
X-Skyfleet-Signature: <hmac-sha256 hex of the raw body>
X-Skyfleet-Delivery-Attempt: 1
Body:
{
"event": "shipment.delivered",
"timestamp": "2026-07-08T09:15:00.000Z",
"data": {
"shipment": { // fields are nested under data.shipment
"id": "ckv…",
"shipmentNumber": "SKY100234",
"awbNumber": "DL1234567890",
"orderReferenceNumber": "ORD-1042",
"shipmentType": "FORWARD",
"status": "DELIVERED",
"paymentMode": "COD",
"courier": "Delhivery",
"codAmount": 1499,
"deliveredAt": "2026-07-08T09:14:55.000Z",
"..." : "..."
}
}
}Verify the signature
Compute an HMAC-SHA256 of the raw request body using your endpoint's secret and compare (constant-time) to the X-Skyfleet-Signature header. Reject if they differ.
// Node.js (Express)
import crypto from "crypto";
app.post("/skyfleet-webhook", express.raw({ type: "*/*" }), (req, res) => {
const expected = crypto.createHmac("sha256", process.env.SKYFLEET_WEBHOOK_SECRET)
.update(req.body) // raw Buffer, not parsed JSON
.digest("hex");
const got = req.headers["x-skyfleet-signature"];
if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(String(got)))) {
return res.status(401).end();
}
const event = JSON.parse(req.body.toString());
// ... handle event.event / event.data ...
res.sendStatus(200); // 2xx = acknowledged
});Respond 2xx to acknowledge. Non-2xx, timeouts, and network errors are retried with backoff (4xx is treated as a caller bug and not retried). Every attempt is logged in Settings → Webhooks → Deliveries, where you can inspect and manually retry.
Questions or need a sandbox key? Email support@skyfleetnow.com. See also our Security and Privacy pages.