Pidge is an Indian last mile delivery orchestration platform. A business connects its own captive rider fleet and a set of third party delivery partners (3PL networks such as Loadshare, Zomato, Dunzo style hyperlocal fleets and Pidge's own "Pidge Powered" networks) to one control layer, and Pidge decides which fleet fulfils each order. For a seller this matters because Pidge is the shipping layer rather than the sales channel: it holds the shipment, the rider, the live location and the delivery outcome for hyperlocal and same city orders, and it exposes all of that over a documented REST API. There is no listings, price or stock surface here at all.
At a glance
What it is
Pidge (Pidge Technologies, Gurugram) is a delivery management and orchestration platform for India, not a marketplace. The public site claims more than 50 million deliveries processed and integration with over 1000 delivery partners. The customer base is D2C brands, restaurants and bakeries, pharmacy, grocery and quick commerce operators: named logos include Bakingo, Vetic, Defence Bakery and Crave by Leena. The product covers order intake, smart allocation between captive riders and 3PL networks, route planning, live tracking, COD handling and analytics, and it supports 1PL, 3PL and hybrid models in the same account.
The commercially important detail for an integrator is that Pidge sits one level above the carriers. A single Pidge order can be fulfilled by the business's own rider, by a Pidge Powered network, or by a third party such as Loadshare, and the API surfaces that choice explicitly through quote, serviceability and fulfil calls.
API access
Credentials are self service from inside a funded Pidge business account. The documented route, from the Pidge help centre page on generating an API token, is: Settings, then Channel Integration, then Generate Token. During token creation the business sets a channel name (the token is scoped to that channel), an allocation configuration of either Smart Allocation or Manual, minimum thresholds for package weight and package volume that are applied when the create order call omits them, and the webhook URL plus the auth token Pidge will send with webhook calls. Pidge then returns a username and password pair once. The help centre states the passwords are not stored, so the credential must be captured at that moment.
Notes that matter when designing a credential store:
- The credential is per channel, not per company. An enterprise account with several brands can hold several tokens, and the order body carries
brand.code,brand.location_codeandbrand.nameto address child accounts under one enterprise token. - The allocation mode is a property of the token, not of the request. A token configured for auto allocation will try to allocate immediately at create time; a manual token leaves the order in
PENDINGuntil a fulfil call is made. That materially changes the integration shape, so record it alongside the credential. - Staging access is not self service. The collection says to reach out to Pidge for staging environment credentials and URL.
- No official SDK is published. The only first party artefact is the Postman collection.
- API version in force is
v1.0in the path. No deprecation schedule is published. - Several calls are explicitly chargeable against the account wallet or credit line: Get Quote, Get Serviceability, and increased frequency rider tracking. They return 4xx when the wallet balance or credit line is insufficient, so treat a 4xx from those calls as a billing condition and not only a validation failure.
The subdomain pidge.readme.io resolves and serves a ReadMe project titled "Pidge", but every page on it is ReadMe's stock starter content (get-users, create-note, list-team-notes). It is not Pidge's API documentation and must not be used as a source. The real reference is the Postman collection at api-docs.pidge.in.
Authentication
- Obtain the channel username and password from the dashboard as described above.
- Exchange them for a bearer token at
POST /v1.0/store/channel/vendor/login. This endpoint takes no auth. - Send the returned string as the
Authorizationheader on every other call. The documented response already contains the wordBearer, so the wholedata.tokenstring is used as the header value rather than being prefixed again. - Cache the token. The collection states tokens are generally unchanging, but they can expire through inactivity or through a configuration change such as editing default values or the webhook. Every other endpoint then returns 401.
- On 401, call login again and retry once. If the password has been revoked outright the login response will not contain
data.tokenat all, which is the signal to stop and alert rather than to loop.
There is no OAuth, no refresh token, no scope list and no token lifetime published.
curl -X POST https://api.pidge.in/v1.0/store/channel/vendor/login \
-H 'Content-Type: application/json' \
-d '{"username":"<channel_username>","password":"<channel_password>"}'
{
"data": {
"token": "Bearer token",
"status": "SUCCESS",
"message": "success Login"
}
}
An authenticated call, using the token verbatim:
curl https://api.pidge.in/v1.0/store/channel/vendor/order/cLB7XafUnOyLDnaQAr_mq \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer <token>'
Multi account handling is by holding one username, password and token triple per Pidge channel, plus the brand codes valid under that channel.
Objects we can read
There are no orders in the commerce sense, no products, no inventory and no settlements. A Pidge "order" is a delivery job, so it maps to the unified shipments table.
Shipments and tracking
GET /v1.0/store/channel/vendor/order/:id, where :id is the Pidge id returned by create order.
The response echoes everything supplied at creation and adds a fulfillment object (or fulfillment_histories when there have been several attempts) holding the current status, the rider, locations and a logs array that is the event history. The parent status key is coarse and documented as:
An order can move back from FULFILLED to PENDING if the client unallocates it. COMPLETED on its own does not mean delivered, since disposed and RTO delivered also land there, so the fulfilment level status is the one to read for the real outcome.
Fulfilment level statuses seen in the logs and in the staging simulator, in order: registered, out for pickup, reached pickup, picked up, ofd (out for delivery), reached delivery, undelivered, delivered, rto out for delivery, rto delivered. That list carries both the NDR signal (undelivered) and the RTO signal.
Trimmed sample (PII in customer_detail, sender_detail, poc_detail):
{
"data": {
"id": "y-qwEQnDd0nA-FjLVNfuM",
"dd_channel": { "name": "some channel name", "order_id": "9", "user": { "id": 513, "type": 4 } },
"reference_id": "dd9pdg637",
"bill_amount": 700,
"cod_amount": 0,
"created_at": "2023-06-08T11:07:28.283Z",
"customer_detail": { "name": "Mithilesh", "mobile": "8887772221" },
"sender_detail": { "name": "Ramesh", "mobile": "8887772221" },
"status": "fulfilled",
"updated_at": "2023-06-09T05:01:29.481Z",
"notes": [],
"fulfillment": {
"channel": { "name": "captive", "order_id": "114916" },
"track_code": "45sgsFF",
"delivery_charge": 44.51,
"logs": [
{ "timestamp": "2023-06-09T04:54:13.182Z", "status": "CREATED", "channel": { "name": "captive", "order_id": "114916" }, "attemptType": "FORWARD" },
{ "timestamp": "2023-06-09T04:56:06.803Z", "status": "OUT_FOR_PICKUP", "location": { "latitude": 28.442554, "longitude": 77.08023 }, "remark": "Start for Pickup" }
]
}
}
}
fulfillment.channel.name is the actual carrier (captive, loadshare, zomato and so on), fulfillment.channel.order_id is that carrier's own reference, and track_code is the waybill style code. delivery_charge is the shipping cost for the job.
There is no list endpoint in the collection. Shipments must be read by id, which means the webhook is the practical way to stay current and the GET is the reconciliation path.
Live rider location
GET /v1.0/store/channel/vendor/order/:id/fulfillment/tracking
{
"data": {
"rider": { "name": "Huzaifa Test", "mobile": "9999999999" },
"status": "PICKED_UP",
"location": { "latitude": 28.5158785, "longitude": 77.0825508 }
}
}
Completed orders return an empty response. This call is rate limited to once per 30 seconds per order across all IPs; higher frequency is available for an additional charge.
Rates and serviceability
Three distinct calls, which are easy to confuse:
POST /v1.0/store/channel/vendor/quotetakes pickup coordinates and pincode plus an array of drops, each withattributes.cod_amount,weightandvolumetric_weight, and returns the partners currently able to serve that lane with a full price breakup. It needs no order to exist. Chargeable.GET /v1.0/store/channel/vendor/order/fulfillment/services?ids=:idis the same idea but against an already created order, and it additionally returns a short lived JWTtokenper partner which must be passed to the fulfil call. Chargeable. The collection warns the two quotes may differ because the order carries real details and the query happens at a different moment.POST /v1.0/store/channel/vendor/estimateis a sub 0.5 second pre order estimate from pickup and drop coordinates only, returning time and cost ranges rather than a bookable quote.
{
"data": {
"items": [
{
"network_id": "-1",
"network_name": "pidge",
"service": "pidge",
"pickup_now": true,
"quote": {
"price": 70.8,
"distance": 13723.42667964,
"eta": { "pickup": "2023-08-28T12:11:26.837Z", "pickup_min": 41, "drop": null, "drop_min": null },
"price_breakup": { "base_delivery_charge": 60, "total_gst_amount": 10.8, "surge": 0, "additional_charges": [] }
},
"error": null,
"token": "eyJhbGciO.....UjOw"
}
]
}
}
Estimate response:
{
"data": {
"pickupToDropTime": 2700,
"pickupToDropDistance": 4500,
"timeToAssign": 1320,
"timeToPickup": 361,
"minCost": 25,
"maxCost": 50
}
}
A fourth call, POST /v1.0/store/channel/vendor/serviceability ("Hybrid Serviceability"), is a low latency yes or no gate for enterprise accounts taking pickup and drop lat and lng, distance in metres and a brand. It returns {"serviceable": true} and, on a 400, {"serviceable": false, "nonServiceableReason": "Rider Not Available"}. The documentation is explicit that it always returns true unless an account manager configures it.
Returns and cancellations
RTO is not a separate object. It appears as the fulfilment statuses rto out for delivery and rto delivered, and lands the parent order in COMPLETED. The undelivered attempt (NDR) is the log entry with status undelivered. There is no published NDR action endpoint (no reattempt or address change call) in the collection.
Support tickets
GET /v1.0/store/channel/vendor/ticket lists order level tickets with pidge_id, issue_category, issue_subcategory, description, ticket_id, status, created_at and updated_at. Categories are PRE_PICKUP (NO_RIDER_ALLOCATED, RIDER_NOT_REACHED_PICKUP, RIDER_DENIED_PICKUP) and POST_PICKUP (RIDER_NON_RESPONSIVE, RIDER_BEHAVIOR_ISSUE, DELAYED_BREACHED_DELIVERY, RTO_NOT_DONE, FINAL_STATUS_MISMATCH_MISS), plus OTHERS.
Locations
Not a first class object. The nearest equivalents are the brand.code and brand.location_code pair sent on every order, and GET /v1.0/store/channel/vendor/rider/list, which returns the business's own riders with id, name, mobile, is_active, home_location, on_task and current_location, filterable by brand_code, brand_name, shift, is_online and rider_id.
Payments and settlements
Not exposed. Delivery cost is visible per shipment as fulfillment.delivery_charge and in the quote breakup, and the account is funded through a wallet or credit line, but there is no statement, invoice or remittance endpoint. COD is carried as cod_amount on the order; there is no COD remittance API.
Writing back: listings, price and stock
Not applicable, this is a carrier. Pidge has no catalogue, no price and no stock surface. The write path is the shipment lifecycle.
Create. POST /v1.0/store/channel/vendor/order with brand, channel, sender_detail (address with line 1 and 2, label, landmark, city, state, country, pincode, latitude, longitude, instructions_to_reach, plus name, mobile, email), poc_detail, and a trips array each holding receiver_detail with the same address shape. Money fields are bill_amount and cod_amount, and there are optional products, packages and notes arrays. The order is created in PENDING. If the token is configured for auto allocation the order immediately tries to allocate and reaches FULFILLED when it succeeds; if allocation fails it still exists, in PENDING.
The response shape is a map keyed by the caller's own source_order_id, because the call accepts bulk:
{ "data": { "PP1010": "cLB7XafUnOyLDnaQAr_mq" } }
Pidge does not enforce uniqueness of source_order_id. Two identical create calls produce two independent deliveries. The caller must claim its own idempotency key and persist the returned Pidge id before any retry, or a timed out create will silently double book.
Allocate. With a manual allocation token, allocation is a separate step. Either POST /v1.0/store/channel/vendor/order/fulfill with {"ids":[":id"],"service":":service_name","pickup_now":true,"token":"<jwt from serviceability>","network_id":"6"}, where the token and network id come from the serviceability call (both omitted when allocating to a captive rider), or POST /v1.0/store/channel/vendor/order/fulfill/smart with {"ids":[":id"],"smart_allocation_id":":smart_id"}, which uses a ranked allocation rule configured on the dashboard and skips the serviceability round trip. Both fail when the wallet balance is insufficient.
Update. PUT /v1.0/store/channel/vendor/order/:id. bill_amount, products, packages and notes can be changed at any point (notes are rewritten wholesale, not merged). reference_id can only change while the order is PENDING. cod_amount can be raised above zero only on self fulfilled orders, can be dropped to zero at any point on self fulfilled or Pidge Powered orders, and cannot be changed at all on 3PL orders.
Unallocate. PUT /v1.0/store/channel/vendor/:id/fulfillment/cancel reverts FULFILLED back to PENDING so the order can be allocated again. Only allowed while the fulfilment status is CREATED, OUT_FOR_PICKUP or REACHED_PICKUP.
Cancel. POST /v1.0/store/channel/vendor/:id/cancel, allowed only from PENDING or FULFILLED, terminal:
{ "data": "Order Cancelled" }
{ "error": { "code": "order.action.cancel.not-allowed", "message": "Order cannot be cancelled " } }
A partial delivery workflow exists (products array supplied at creation, bill and COD amounts summing the individual products) but it is gated: the collection says to contact the account manager for enablement. Route level calls (/channel/vendor/order/single-route, /channel/vendor/order/unroute/:route_id, /channel/vendor/order/route-allocate) wrap the dashboard's own route service for batching stops onto one rider.
There is no label or manifest PDF endpoint in the collection. fulfillment.track_code is the waybill reference, but label generation is a dashboard function.
Webhooks and notifications
Three push channels, all configured on the dashboard rather than through an API:
- Order status update. Set the URL and an auth token when generating the API credential. Pidge POSTs the same body as the GET order call, except that the keys sit at the top level rather than inside a
datawrapper. That difference is a real parsing trap: the same normaliser cannot be reused without unwrapping. - Ticket status update. POSTed to the
update_info.callback_urlsupplied when the ticket was created, withAuthorizationset to thecallback_authsupplied at the same time. The body carriesticket_id,status,order_statusand the category fields. - Rider task webhook. Configured in the communications module of the dashboard, for rider level task events.
Verification is a shared bearer token that the client chooses and Pidge echoes back in the Authorization header. There is no HMAC signature and no signing secret, so the receiving endpoint must compare the token in constant time and must not rely on source IP.
No retry policy, delivery guarantee or replay endpoint is published. Treat the webhook as at most once and reconcile: for any shipment not in a terminal state, poll GET /order/:id on a slow schedule (hourly is ample for same day work, the events carry their own timestamps) and treat the GET as the source of truth on conflict.
On staging only, two simulators make this testable without real riders. GET /v1.0/store/channel/vendor/order/:id?dummy_status=<status> returns a GET response with simulated fulfilment data, and POST /v1.0/store/channel/vendor/order/:id/webhook/events with {"dummy_status":"<status>"} fires a simulated webhook at the configured URL. Accepted values are cancelled, pending, fulfilled|registered, fulfilled|out for pickup, fulfilled|reached pickup, fulfilled|picked up, fulfilled|ofd, fulfilled|reached delivery, fulfilled|undelivered, fulfilled|delivered, fulfilled|rto out for delivery, fulfilled|rto delivered.
Rate limits and pagination
Only one numeric limit is published: rider location tracking is capped at one call per 30 seconds per order, counted across all IPs, and can be raised for an extra fee. No global request ceiling, no quota headers and no burst allowance are documented, and the collection exposes no quota response header.
Pagination does not arise, because there is no list endpoint for shipments. Reads are by id. The list endpoints that do exist, tickets and riders, are filtered by query parameter and return a plain data array with no published cursor.
The practical cadence: rely on the order status webhook for state, reconcile unterminated shipments with GET /order/:id hourly, and never put the rider tracking call on a loop tighter than 30 seconds per order.
Mapping to the unified model
Gaps and open questions
- No token lifetime is published. The collection only says tokens are "generally unchanging" and expire on inactivity or configuration change, which is not something a refresh job can be scheduled against. The only safe design is reactive re-login on 401.
- No shipment list or date window query. Backfilling history, or recovering after a webhook outage longer than the retention of local ids, has no documented route short of asking Pidge for an export.
- No published global rate limit. Bulk create is supported but no batch size ceiling is stated.
- Webhook retry behaviour is undocumented, and the auth is a shared bearer token with no signature, so there is no way to prove provenance cryptographically.
- No label, manifest or proof of delivery artefact endpoint. Whether a delivery image or signature is captured, and how to retrieve it, is not in the collection.
- Partial delivery, hybrid serviceability and increased tracking frequency are all account manager gated, so their behaviour cannot be verified without an onboarded account.
- The staging base URL is not published and must be requested from Pidge.
- Two third party GitHub repositories that integrate Pidge agree with the collection on the base host and core paths, which raises confidence in those paths, but their surrounding claims (for example a 900 g default volumetric weight for an M parcel category) are account specific and were not verifiable against first party documentation.
Sources
- Pidge Integration APIs, published Postman collection and its JSON at
documenter.gw.postman.com/api/collections/54282256/2sBYArTXix, read in full on 2026-09-22 - Pidge help centre: Generate API Token
- Pidge product site
https://api.pidge.in/v1.0/store/channel/vendor/orderreturns 401 unauthenticated, confirming the documented base host and path are live- Third party integrations used only to corroborate paths, not as primary sources:
WhistleFetch-Technologies/warmpawzaws(backend/lambda/src/lib/services/pidge-logistics.ts) andrmorampudi09-arch/Craves-Build-platform(services/integration-service/modules/pidge-hyperlocal/README.md) on GitHub