Delhivery is the largest integrated logistics company in India by revenue and by parcel volume, and for most Indian D2C brands it is either the default carrier or one of two. For a commerce database it is a carrier connector, not a sales channel: there are no listings, no stock and no marketplace orders here. What it gives us is a serviceability answer for a pincode before checkout, an approximate rate, a waybill (AWB) and a shipping label when we book, a pickup request against a registered warehouse, a scan by scan tracking history that can be pulled or pushed, and a small set of actions on failed deliveries. The Express (B2C) API is genuinely public: Delhivery publishes an OpenAPI backed reference with request and response examples, and every endpoint, header and field on this page was read from it.
At a glance
Two things surprise people on the first integration. First, order creation is not a JSON POST: the body is form encoded as format=json&data=<url-encoded JSON>, and sending plain JSON to /api/cmu/create.json fails. Second, pickup_location.name must match a warehouse already registered against your client account, character for character including case, or manifestation is rejected.
What it is
Delhivery Private Limited was founded in 2011 and listed on the NSE and BSE in May 2022. It runs a fully integrated network: first mile pickup, its own line haul fleet, sortation centres, last mile delivery, plus warehousing, freight (part truckload and full truckload), cross border and supply chain software. Its parcel network covers close to every serviceable pincode in India, which is why serviceability lookups against Delhivery are often used as a proxy for "can this address be served at all".
For a seller, Delhivery appears in two quite different shapes. Direct clients get a client account, an API token and negotiated rates, which is what this page documents. Smaller sellers reach Delhivery through an aggregator (Shiprocket, NimbusPost, Shipway, iThink, EasyEcom and others), in which case there is no Delhivery token at all and the shipment is created through the aggregator's API with Delhivery named as the courier. If a brand tells you "we use Delhivery", establish which of the two it is before designing the connector, because the credential shape, the AWB ownership and the remittance path are all different.
There is a third shape, Delhivery B2B, for part truckload and full truckload freight. It has its own API and its own documentation portal, and that portal is currently closed (see Gaps).
API access
There is no developer console and no self serve signup. The documented prerequisites are:
- Client account information. Your Delhivery business development or client services relationship manager issues the client account name (the HQ name), a user id and the API token. Delhivery describes the token as a 12 to 16 character string and as the only authentication parameter for the whole API.
- Two tokens in sequence. A staging token is issued first, for
staging-express.delhivery.com. After integration testing and UAT complete, a separate production token is issued fortrack.delhivery.com. The tokens are not interchangeable and the staging environment does not move real freight. - At least one registered warehouse. Manifestation is only allowed from a pickup location that already exists in Delhivery's system. You can have it created for you by emailing
vendordesk@delhivery.com, or create it yourself through the client warehouse API.
Base URLs:
The API is versioned loosely. Paths carry their own version fragments (/api/v1/packages/json/, /api/cmu/create.json, /api/kinko/v1/invoice/charges/.json) and Delhivery has not published a deprecation calendar for the Express API. There are no official Delhivery SDKs on the company's GitHub organisation; the maintained community clients named in Sources are the practical starting point.
Delhivery also ships first party plugins for Shopify, WooCommerce and OpenCart, documented in the same reference under "Guide to plugin". Those are the right answer for a brand that only needs label printing, and the wrong answer if you need the scan history in your own database.
Authentication
There is nothing to exchange and nothing to refresh. The token you are issued is sent directly.
- Obtain the token from your Delhivery relationship manager (staging first, production after UAT).
- Send it on every request as
Authorization: Token <api-token>. The wordTokenand the space are part of the value. - Several of the older endpoints, notably the waybill and tracking endpoints, additionally accept
?token=<api-token>as a query parameter. Prefer the header; the query form leaks the secret into access logs and referrers. - There is no expiry, no refresh endpoint and no scope system. Rotation means asking Delhivery for a new token.
Multi account is simple as a consequence: one token is one Delhivery client account, and a brand with separate accounts for express and reverse logistics, or for two legal entities, is two credentials in your store. There is no concept of an app acting on behalf of several sellers, so there is no OAuth style consent flow to build.
A pincode serviceability check, which is the cheapest way to prove a token works:
curl -G 'https://staging-express.delhivery.com/c/api/pin-codes/json/' \ --data-urlencode 'filter_codes=110017' \ -H 'Authorization: Token 0123456789abcdef0123456789abcdef01234567' \ -H 'Content-Type: application/json'
An authenticated tracking call:
curl -G 'https://track.delhivery.com/api/v1/packages/json/' \ --data-urlencode 'waybill=72510016030' \ -H 'Authorization: Token 0123456789abcdef0123456789abcdef01234567'
The reference's own OpenAPI definitions declare a query parameter named api_key in the security scheme block. That is a readme.io scaffolding artefact, not a real Delhivery parameter. Every documented example, and every working open source client, uses the Authorization: Token ... header or the token query parameter. Do not send api_key.
Objects we can read
There are no orders, order items, products, listings, inventory or settlements in this API. What follows is everything Delhivery does expose.
Shipments and tracking
Two ways to read a shipment: pull and push. Pull first.
GET /api/v1/packages/json/
There is no pagination and no date window. This is a lookup by key, not a list endpoint, which is the single most important design constraint on a Delhivery connector: you cannot ask Delhivery for "everything that changed since yesterday". You track the AWBs you know about. Delhivery caps the pull API at 750 requests per 5 minutes per IP.
A trimmed successful response. Field names and example values are taken from the schema published in the reference; consignee fields are PII.
{
"ShipmentData": [
{
"Shipment": {
"AWB": "72510016030",
"ReferenceNo": "26288668",
"OrderType": "COD",
"Origin": "BLR_Hub (Karnataka)",
"Destination": "Surat",
"SenderName": "100bestbuy",
"Status": {
"Status": "RTO",
"StatusType": "RTO",
"StatusCode": null,
"StatusLocation": "Bengaluru_East (Karnataka)",
"StatusDateTime": "2013-11-11T17:13:31",
"RecievedBy": "",
"Instructions": "Cancelled the order|Shipment received | Package dispatched for RTO"
},
"Consignee": {
"Name": "sanjay",
"Address1": ["h/no-152,Dashrath Nagar Society,kapodra char rasta,varachha"],
"Address2": ["road,surat"],
"Address3": "",
"City": "Surat",
"State": "",
"PinCode": 395006,
"Telephone1": ["26288668"],
"Telephone2": ""
},
"PickUpDate": "2013-10-17T17:14:21",
"OriginRecieveDate": "2013-10-18T10:44:25.328000",
"DestRecieveDate": null,
"OutDestinationDate": null,
"FirstAttemptDate": null,
"ReturnedDate": null,
"ReverseInTransit": false,
"DispatchCount": 1,
"ChargedWeight": 0,
"CODAmount": 818,
"InvoiceAmount": 818,
"EWBN": null,
"Scans": [
{
"ScanDetail": {
"Scan": "RTO",
"ScanType": "RTO",
"ScanDateTime": "2013-11-11T17:13:31",
"StatusDateTime": "2013-11-11T17:13:31",
"ScannedLocation": "Bengaluru_East (Karnataka)",
"Instructions": "Package dispatched for RTO in Incoming",
"StatusCode": null
}
}
]
}
}
]
}
Reading that response:
Status.Statusis the current human readable state.Status.StatusTypeis the coarse lane:UDfor undelivered and in transit,DLfor delivered,RTorRTOfor return to origin,PPfor pending pickup.Status.StatusCodeis the machine code, for exampleEOD-74,EOD-15,ST-108. These codes are what the NDR API keys off, so store them rather than the prose inInstructions.Scans[]is the event history, oldest first. Each entry nests the real content underScanDetail, which is easy to miss.ChargedWeightis Delhivery's billed weight, which is where weight disputes start. It is often0until the shipment has been weighed in a sortation centre.- Note
RecievedByis spelled that way in the payload. It is the proof of delivery name field: on a delivered shipment it carries the name of the person who signed for the package. There is no separate proof of delivery endpoint and no signature image in this API; the delivered scan plusRecievedByandStatus.StatusDateTimeis the proof of delivery you get.
An error response has a completely different shape and no HTTP hint in some cases, so branch on the presence of ShipmentData:
{ "Error": "parameter ref_ids/ref_nos or waybill is required" }
Pincode serviceability
GET /c/api/pin-codes/json/
Calling it with no filter returns the full serviceable list, which is the right way to seed a local serviceability table and refresh it with dt afterwards.
{
"delivery_codes": [
{
"postal_code": {
"pin": 110017,
"district": "South Delhi",
"state_code": "DL",
"country_code": "IN",
"inc": "Delhi_Airport_GW (Delhi)",
"sort_code": "DEL/KIS",
"pre_paid": "Y",
"cash": "Y",
"cod": "Y",
"pickup": "Y",
"repl": "Y",
"is_oda": "N",
"max_amount": 0.0,
"max_weight": 0.0,
"covid_zone": null,
"center": [
{
"code": "IND110030AAC",
"cn": "Delhi_MalviyaNagar (Delhi)",
"sort_code": "DEL/KIS",
"s": "2019-06-28T13:23:26.690",
"ud": "2019-06-28T13:23:26.690",
"u": "rajat"
}
]
}
}
]
}
The flags are the point: pre_paid, cash and cod say whether prepaid and cash on delivery forward shipments are accepted, pickup whether reverse pickup is possible, repl whether replacement is supported, is_oda whether the pincode is out of delivery area (which carries a surcharge). An unserviceable or invalid pincode returns {"delivery_codes": []}, not an error.
Shipping charges
GET /api/kinko/v1/invoice/charges/.json
The response is an array; callers read [0].total_amount and [0].gross_amount. Delhivery is explicit that this is approximate and that the amount actually invoiced can differ, and that only the track.delhivery.com host carries current rate cards. The limit is 40 requests per minute per IP, after which the IP is throttled for a minute. That is low enough that a checkout time rate call needs a local cache.
Waybill inventory
Two ways to obtain AWB numbers before you manifest.
GET /waybill/api/bulk/json/?cl=<client_name>&count=<n> returns a block of waybills. Limits: 10,000 per request, 50,000 per 5 minutes, and crossing that throttles your IP for a minute.
GET /waybill/api/fetch/json/?cl=<client_name> returns exactly one.
Both are optional. If you omit waybill at order creation, Delhivery assigns one dynamically and returns it, and that is the simpler path. The exception is multi piece shipments: dynamic assignment is not available there, so every box must carry a waybill you fetched first. Delhivery also warns that waybills fetched from this API differ from any file handed over at onboarding.
Pickup requests
There is no list endpoint. Creation returns the record, and that is the only read.
{
"pickup_id": 4323300,
"client_name": "100bestbuy",
"pickup_location_name": "new new",
"incoming_center_name": "Kolkata_Sonarpur_DC",
"pickup_time": "18:30:00",
"pickup_date": "2018-08-29",
"expected_package_count": 10
}
Locations (client warehouses)
Also creation only. POST /api/backend/clientwarehouse/create/ echoes the stored record back as {"data": {"name": ..., "pincode": 324005, "phone": ..., "address": ..., "client": ..., "active": true, "working_hours": null, "type_of_clientwarehouse": null, "largest_vehicle_constraint": null, "message": "A new client warehouse has been created in HQ(Delhivery)."}, "success": true, "error": ""}. To enumerate your warehouses, you read them out of the Delhivery client panel or keep your own registry. There is no GET for this resource.
Returns and cancellations
Delhivery models returns two ways and neither is a separate object you can list.
- RTO (return to origin), when delivery fails and the package comes back. It appears on the same AWB as scans with
StatusTypeofRTorRTO, and aReturnedDate. - Reverse pickup, when the customer sends something back deliberately. This is a new shipment created with
payment_modeset toPickup, with origin and destination swapped, carrying its own AWB.
So returns are read through the tracking endpoint, keyed by AWB, like everything else.
Payments and settlements
Not available in this API. COD remittance statements, weight dispute credits and freight invoices are delivered through the Delhivery client panel and by email, not over the Express API. CODAmount on a tracking record tells you what should be collected, not what has been remitted. Reconciling cash collected against cash received is a panel download or a finance team file, and that is a real gap for anyone building a margin model.
Writing back: listings, price and stock
Not applicable, this is a carrier. There are no listings, no prices and no stock. The write path is the shipment lifecycle.
Create a shipment (manifestation)
POST /api/cmu/create.json
This is the one endpoint that is not JSON. The body is application/x-www-form-urlencoded with two fields, and the documentation is blunt that format=json&data= is mandatory:
POST /api/cmu/create.json HTTP/1.1 Host: track.delhivery.com Authorization: Token 0123456789abcdef0123456789abcdef01234567 Content-Type: application/x-www-form-urlencoded Accept: application/json format=json&data=%7B%22pickup_location%22%3A%7B%22name%22%3A%22MAIN-WH%22%7D%2C%22shipments%22%3A%5B%7B...%7D%5D%7D
The decoded data object, from the sample published for a forward flow:
{
"pickup_location": { "name": "name of pickup/warehouse location registered with delhivery" },
"shipments": [
{
"order": "123467800",
"order_date": "2018-05-18 06:22:43",
"name": "Customer_name",
"add": "jaipur, ",
"city": "Delhi",
"state": "Delhi",
"country": "India",
"pin": "110096",
"phone": "1111111111",
"payment_mode": "COD",
"cod_amount": "1.0",
"total_amount": "1.0",
"products_desc": "product description",
"quantity": "1",
"return_add": "address",
"return_city": "Delhi",
"return_state": "Delhi",
"return_country": "India",
"return_pin": "110096",
"return_phone": "1111111111",
"seller_name": "",
"seller_add": "",
"seller_inv": ""
}
]
}
Fields worth knowing beyond the sample, all documented in the full request body example:
Rules that bite:
ordermust be unique per manifested order when Delhivery is assigning the waybill. It may repeat only when you supply the waybill yourself, because the primary key in Delhivery's network is order plus waybill.pin,phoneandaddare mandatory in every flow.- The characters
&,#,%,;and backslash are rejected in the request body unless the body is properly JSON encoded. Encode it properly and this stops being a problem.
The response is a manifestation summary. A failure looks like this, and note that success is false while the HTTP status is still 200:
{
"success": false,
"error": true,
"upload_wbn": "UPL2091701343621419496",
"rmk": "An internal Error has occurred, Please get in touch with client.support@delhivery.com",
"packages": [],
"package_count": 0,
"prepaid_count": 0,
"cod_count": 0,
"cash_pickups_count": 0,
"cash_pickups": 0,
"pickups_count": 0,
"replacement_count": 0,
"cod_amount": 0
}
On success, packages[] is populated and packages[0].waybill is the AWB you store, alongside the counters (prepaid_count, cod_count, pickups_count) which tell you how many of each type were accepted. Always branch on the success boolean, never on the HTTP status.
Edit a shipment
POST /api/p/edit with JSON. Documented editable fields include add, product_details, shipment_width, tax_value and similar. The same path is used for cancellation, which is the API's one genuinely confusing overload.
Cancel a shipment
POST /api/p/edit with JSON:
{ "waybill": "72510221966", "cancellation": "true" }
Cancellation is allowed while the package is in Manifested, In Transit, Pending, Open or Scheduled. The effect differs by type: a prepaid or COD forward shipment moves to Returned, a pickup (reverse) shipment moves to Cancelled.
{
"status": true,
"waybill": "72510221966",
"remark": "Shipment has been cancelled",
"order_id": "6543536547676"
}
Request a pickup
POST /fm/request/new/ with JSON. Four required fields:
{
"pickup_location": "MAIN-WH",
"pickup_date": "2026-09-22",
"pickup_time": "18:30:00",
"expected_package_count": 10
}
Returns 201 with the record shown earlier. Behaviour to design around: for a single warehouse, a second pickup request can only be raised after the previous one has been completed. Several warehouses can each have an open request at the same time. A wrong warehouse name returns 401 with {"pickup_location": "Invalid Pickup Location ..."}, and sending the wrong content type returns 415.
Print a label
GET /api/p/packing_slip?wbns=<waybill> returns JSON describing the packing slip, which you render to HTML yourself and barcode with Code 128. Delhivery does not return a ready made PDF here; the client panel does.
Register a warehouse
POST /api/backend/clientwarehouse/create/ and POST /api/backend/clientwarehouse/edit/, both JSON. Fields: name, registered_name, email, phone, address, city, pin, country, and the return address set return_address, return_pin, return_city, return_state, return_country. Delhivery rejects any key not on that list, so do not pass extras through.
Act on an NDR
POST /api/p/update takes an action on a package that failed delivery. It is asynchronous and supports partial success, so it always returns a UPL id rather than a result.
Both DEFER_DLV and RE-ATTEMPT require the package to carry one of a specific set of status codes, which you read from the tracking response:
DEFER_DLV:EOD-74,EOD-15,EOD-11,EOD-3,EOD-16,EOD-6,ST-108RE-ATTEMPT:EOD-74,EOD-15,EOD-104,EOD-43,EOD-86,EOD-11,EOD-16,EOD-69,EOD-6,ST-108
Then poll GET /api/cmu/get_bulk_upl?UPL=<upl-id>&verbose=true:
{
"status": "Partial",
"remark": "2 waybills affected",
"request_id": "UPL6601045209493869031",
"success_wbns": [
{ "waybill": "12410125716", "message": "", "action": "DEFER_DLV" },
{ "waybill": "12410124541", "message": "", "action": "EDIT_DETAILS" }
],
"failed_wbns": [
{ "waybill": "12410124541", "message": "Package in incorrect status", "action": "RE-ATTEMPT" }
]
}
Webhooks and notifications
Delhivery pushes scans. There is one event, "a new scan was applied to a package", and it fires on every scan.
- Subscription is manual. There is no endpoint to register a URL. You give Delhivery the HTTPS endpoint, any headers it needs (they explicitly support a custom authorization header and content type), and one or two live production waybills to test against. Delhivery quotes a minimum of five to six working days for development, testing and deployment of the webhook after requirements are frozen.
- Verification is whatever header you asked Delhivery to send. There is no HMAC signature and no shared secret scheme. Treat the endpoint as unauthenticated by default and validate by re-pulling the AWB if the scan matters.
- The body is one scan, not a batch:
{
"Shipment": {
"AWB": "XXXXXXXXXXXX",
"ReferenceNo": "28",
"NSLCode": "X-UCI",
"Sortcode": "IXC/MDP",
"PickUpDate": "2019-01-09T17:10:42.543",
"Status": {
"Status": "Manifested",
"StatusType": "UD",
"StatusDateTime": "2019-01-09T17:10:42.767",
"StatusLocation": "Chandigarh_Raiprkln_C (Chandigarh)",
"Instructions": "Manifest uploaded"
}
}
}
- Expected response is
200 OK. Delhivery does not publish a retry policy, so assume at most once and reconcile. - Delhivery's own guidance is to consume every scan pushed and not to reject scans that fail a strict check on first receipt. Store the raw event and interpret later.
- The body shape is negotiable: the documentation says the format can be changed if the client requires it. Pin the exact shape you agreed in writing, because it is not the same for every client.
If you cannot get a webhook provisioned, poll GET /api/v1/packages/json/. A sensible cadence, given the 750 requests per 5 minutes per IP ceiling and that waybill accepts a comma separated list: batch 20 to 50 AWBs per call, poll active shipments every 30 to 60 minutes and delivered or RTO-complete shipments not at all.
Rate limits and pagination
There is no pagination anywhere in this API. Every read is a lookup by key, or a full dump in the case of the pincode list.
Two consequences for the connector. The limits are per IP, not per token, so several client accounts sharing one egress IP share one budget, and a busy connector should be given a stable dedicated egress address. And because there is no "changed since" query, the sync cadence is entirely driven by the set of open AWBs you are carrying: a brand with 5,000 in flight shipments, batched 50 per call, is 100 calls per sweep, which fits comfortably inside the tracking ceiling at an hourly cadence.
No quota headers are documented, so there is nothing to read back. Implement your own token bucket and back off on any non-200.
Mapping to the unified model
orders, order_items, products, listings, inventory, settlements and customers have no Delhivery source. A Delhivery connection contributes shipments and locations, and enriches orders that were sourced from a sales channel.
shipments
Fields with no unified home that are worth keeping in raw: Sortcode, NSLCode, Origin, Destination, DispatchCount, FirstAttemptDate, ReverseInTransit, EWBN, RecievedBy.
orders (enrichment only)
returns
locations
Gaps and open questions
- No list endpoint, anywhere. You cannot ask "which of my shipments changed", "what warehouses do I have" or "what pickups are open". Everything is a lookup by a key you already hold. A connector must therefore own the AWB registry and reconcile from the sales channel side.
- No settlement or COD remittance API.
CODAmountis what should be collected. What was actually remitted, when, net of what deductions, is only available in the client panel and in finance emails. This is the largest gap for anyone building unit economics. - No proof of delivery artefact. There is no signature image, no photo and no POD document endpoint.
Status.RecievedByon the delivered scan is the whole of it. - B2B API not verifiable. The part truckload and full truckload API has its own portal at
delhivery-b2b-api-doc.readme.io, which on 2026-09-21 redirects to/inactive. Delhivery's main developer portal atone.delhivery.com/developer-portalis a client side application that served no readable content to the fetcher. Freight endpoints, auth model and object shapes for B2B are therefore unverified, and the B2B API is understood to use a different authentication scheme from the ExpressTokenheader. Treat anything you read elsewhere about Delhivery B2B as unconfirmed until you have portal access. - Webhook contract is per client. The documentation states the pushed body can be changed on request. Two Delhivery clients can legitimately receive different shapes, so a multi tenant connector cannot assume one parser.
- No webhook signature. Authentication of pushed scans rests on a header you choose. Anyone who learns your endpoint can post scans to it.
- Rate limits are per IP and only three are published. Limits on order creation, pickup creation, warehouse creation and the NDR endpoints are not documented.
- Status code list is incomplete. The NDR sections enumerate the codes those two actions accept. There is no published master list of every
StatusCodeandNSLCodevalue with its meaning, and you will meet codes not on the NDR lists. - Charges are approximate by the vendor's own admission, and the reference notes the staging host does not carry current rates. Do not reconcile billing against this endpoint.
Sources
Official, all fetched on 2026-09-21:
- Delhivery Express API reference, introduction
- Documentation index (llms.txt)
- Pre-requisites for integration
- Pin-code Serviceability API and its OpenAPI test page
- Bulk WayBill API and Fetch WayBill
- Package Order Creation / Manifestation API and its OpenAPI test page
- Order Tracking API and its OpenAPI test page
- Tracking via PUSH API, webhook
- Cancel Order API and Edit Order API
- Invoice, Shipping Charge API
- Packing Slip API
- Pickup Request Creation API
- Client Warehouse Creation API
- Asynchronous NDR Package Action API and Get NDR status API
- delhivery-b2b-api-doc.readme.io (redirects to
/inactive) - github.com/delhivery, the company's GitHub organisation: eight repositories, none an API SDK
Secondary, used to confirm real world request shapes and the form encoding of order creation:
- nguyendachuy/laravel-delhivery-api, a Laravel client whose resource classes map one to one onto the official reference pages, and which confirms the staging and production base URLs
- elanic-tech/angarum, a production multi carrier integration, for the cancellation, warehouse creation and scan parsing shapes