Shiprocket

Shiprocket API v1: email and password login for a 10 day JWT, orders, AWB assignment, pickup, label and manifest, tracking, serviceability, NDR, returns and webhooks.

Shiprocket is India's largest shipping aggregator: a seller signs up, connects a store, and gets rate-shopped access to Delhivery, Blue Dart, Xpressbees, Ecom Express, DTDC, Ekart, Amazon Shipping, Shadowfax and around twenty more carriers behind one API and one wallet. For a commerce database it sits awkwardly between a carrier and a channel. It is not a sales channel, so there are no marketplace listings to price or stock. But it does hold orders (imported from the seller's own channels), a product catalogue, an inventory counter, pickup locations, shipments with AWBs, returns, NDR records and a wallet statement, which makes it by far the richest object graph of any carrier connector in this set. The whole API is published as a public Postman collection with worked request and response examples for almost every endpoint, and everything on this page was read from it.

At a glance

Note

The single most common integration bug on this API is the two meanings of order_id. The order_id you send at creation is your reference. The order_id that comes back in the response is Shiprocket's internal order id, and every later call expects the Shiprocket one. There is also a third identifier, shipment_id, and it is the one that AWB assignment, pickup, label and manifest all key off, not order_id.

What it is

Shiprocket, operated by Bigfoot Retail Solutions, launched in 2017 and is the dominant shipping aggregator for small and mid sized Indian e-commerce. The proposition is straightforward: the seller does not hold carrier contracts, Shiprocket does, and the seller gets aggregated rates, one pickup, one tracking page, one COD remittance cycle and one prepaid wallet. It serves D2C brands on Shopify, WooCommerce and Magento, sellers on Amazon and Flipkart, and social sellers with no storefront at all.

Its footprint is broad enough that it shows up as an intermediary for a large share of Indian parcel volume, which matters to a commerce database in a specific way: when a brand says "we ship with Delhivery", there is a good chance the AWB was actually minted by Shiprocket and Delhivery is just the chosen courier on that shipment. In that case the connector to build is this one, not the Delhivery one, because the Delhivery token does not exist.

Shiprocket has grown well past last mile. The platform now also covers fulfilment (Shiprocket Fulfillment, marked fbs in the orders API), hyperlocal same day delivery, international shipping with its own KYC and bank detail endpoints, a checkout product, a returns and exchange product, and a set of machine learning APIs branded Sense for RTO prediction and address scoring, which are documented separately on console.shiprocket.in. Pickrr, a competing aggregator, was acquired by Shiprocket in 2022; see /connectors/pickrr.

API access

Self serve, and quick:

  1. Register a Shiprocket seller account and complete onboarding.
  2. In the panel, go to Settings, API, Add New API User, then Create API User.
  3. In the dialog, give the API user a distinct email address, different from your main Shiprocket login. Choose the modules the user may reach, and choose whether Buyer's Details Access is Allowed or Not Allowed.
  4. Copy the generated API password immediately. Shiprocket will not display it again.
  5. Exchange that email and password for a token through the authentication endpoint.

Points worth noting for the connector design. The module selection and the buyer details toggle are a real permission system: an API user created without buyer details access will get orders and shipments with customer name, phone and address suppressed, which silently breaks any address based reconciliation. Check this first when PII comes back empty.

There is no sandbox. Every call is live, against real wallet balance and real carriers. Test with a draft order you cancel, and be aware that AWB assignment debits the wallet.

There are no official Shiprocket SDKs on GitHub. The official artefact is the Postman collection, and Shiprocket also publishes a Model Context Protocol server exposing a subset of the API (rate calculator, order create, list, track, ship, pickup schedule, label, cancel, pickup addresses) as tools.

API version is v1 throughout and there is no published deprecation calendar. Shiprocket versions the collection with a versionTag of latest, which is the only handle on change; there is no changelog endpoint.

Authentication

A plain credential exchange returning a JWT. There is no OAuth, no refresh token and no scope string.

  1. POST https://apiv2.shiprocket.in/v1/external/auth/login with Content-Type: application/json and a body of {"email": ..., "password": ...}, using the API user credentials created in the panel, not your login credentials.
  2. Read the token out of the response.
  3. Send Authorization: Bearer <token> on every later call.
  4. The token is valid for 10 days. There is no refresh endpoint, so renewal means calling login again. POST /v1/external/auth/logout invalidates a token early.
curl -X POST 'https://apiv2.shiprocket.in/v1/external/auth/login' \
  -H 'Content-Type: application/json' \
  -d '{"email":"api-user@example.com","password":"<api-user-password>"}'
curl -X GET 'https://apiv2.shiprocket.in/v1/external/orders?per_page=50&page=1' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...'

Multi account is one credential pair per Shiprocket seller account. There is no concept of an application acting on behalf of many sellers, so a platform integrating on behalf of its merchants stores each merchant's API user email and password, and runs a renewal job that logs in again before day ten. Store the password, not just the token: without it there is no way back once the token expires.

Objects we can read

Orders

GET /v1/external/orders lists every order in the account. GET /v1/external/orders/show?order_id=<id> returns one.

The 30 day cap on updated_from is the key constraint for incremental sync: you cannot ask for changes older than 30 days. Backfill with from/to over creation date, then run the incremental loop on updated_from/updated_to in windows shorter than 30 days.

Products and shipments are nested inside each order, so one list call gives you the full graph:

{
  "data": [
    {
      "id": 16178831,
      "channel_id": 76893,
      "channel_name": "CUSTOM",
      "base_channel_code": "CS",
      "channel_order_id": "224-4779888",
      "customer_name": "Majin Bu",
      "customer_email": "naruto@uzumaki.com",
      "customer_phone": "9988998899",
      "pickup_location": "hell",
      "total": "9000.00",
      "tax": "0.00",
      "sla": "2 days",
      "shipping_method": "SR",
      "status": "CANCELED",
      "status_code": 5,
      "payment_method": "prepaid",
      "is_international": 0,
      "channel_created_at": "24 Jul 2019, 11:11 AM",
      "created_at": "31 Jul 2019, 03:03 PM",
      "products": [
        {
          "id": 18769728, "channel_order_product_id": "18769728",
          "name": "Kunai", "channel_sku": "chakra123", "hsn": "441122",
          "quantity": 10, "product_id": 17484610, "available": 50,
          "status": "CANCELED"
        }
      ],
      "shipments": [
        {
          "id": 16028538, "awb": "", "return_awb": "", "courier": "",
          "weight": 0, "dimensions": "0.00x0.00x0.00", "volumetric_weight": 0,
          "pickup_scheduled_date": null, "pickup_token_number": null,
          "pod": null, "etd": "NA",
          "delivered_date": null, "rto_delivered_date": "0000-00-00 00:00:00"
        }
      ],
      "activities": ["ORDER_CREATED", "ADDRESS_MODIFIED", "ORDER_CANCELLED"]
    }
  ]
}

Customer name, email, phone and the shipping address are PII, and are suppressed if the API user was created without buyer details access.

status_code is the stable field; status is its label. The documented codes, which double as the filter values when filter_by is status: 1 New, 2 Invoiced, 3 Ready To Ship, 4 Pickup Scheduled, 5 Canceled, 6 Shipped, 7 Delivered, 8 ePayment Failed, 9 Returned, 10 Unmapped, 11 Unfulfillable, 12 Pickup Queue, 13 Pickup Rescheduled, 14 Pickup Error, 15 RTO Initiated, 16 RTO Delivered, 17 RTO Acknowledged, 18 Cancellation Requested, 19 Out for Delivery, 20 In Transit, 21 Return Pending, 22 Return Initiated, 23 Return Pickup Queued, 24 Return Pickup Error, 25 Return In Transit, 26 Return Delivered, 27 Return Cancelled, 28 Return Pickup Generated, 29 Return Cancellation Requested, 30 Return Pickup Cancelled.

POST /v1/external/orders/export queues a CSV export, which is the right tool for a first backfill of a large account.

Order items

Order items come back only as the products[] array nested in an order. There is no standalone order item endpoint. channel_sku is the SKU as the sales channel knows it, product_id is Shiprocket's catalogue id when the item has been mapped, and available is the Shiprocket inventory count for that product at the time of reading.

Products and listings

Shiprocket keeps its own catalogue, distinct from the seller's channels.

  • GET /v1/external/products and GET /v1/external/products/show/{product_id} read it.
  • GET /v1/external/listings reads the channel side: the products as they exist on the connected sales channels.
  • GET /v1/external/listings/export/mapped and /export/unmapped produce the reconciliation files between the two.
  • GET /v1/external/channels lists the connected sales channels with their channel_id, which you need for almost every other call.

The distinction matters. A "listing" here is a record of what exists on Amazon or Shopify as Shiprocket sees it, for mapping purposes. It is not a listing you can price or stock through this API.

Inventory

GET /v1/external/inventory returns Shiprocket's own stock counts per product. These drive the available figure on order items and the unfulfillable status. They are Shiprocket's warehouse view, not the seller's marketplace stock.

Shipments and tracking

GET /v1/external/shipments lists shipments, filtered with filter, filter_by, sort and sort_by. It is the one endpoint in the collection with an explicit pagination envelope:

{
  "data": [
    {
      "id": 3322791,
      "order_id": 3324748,
      "awb": "109123535421",
      "status": "CANCELED",
      "created_at": "28th Aug 2018 07:11 PM",
      "channel_id": 76893,
      "channel_name": "CUSTOM",
      "payment_method": "cod",
      "products": [{ "name": "gsdgw", "sku": "123", "quantity": 2 }]
    }
  ],
  "meta": {
    "pagination": {
      "total": 19631,
      "count": 15,
      "per_page": 2,
      "current_page": 1,
      "total_pages": 1309,
      "links": { "next": "https://apiv2.shiprocket.in/v1/external/shipments?page=2" }
    }
  }
}

Tracking has four entry points, all returning the same envelope:

{
  "tracking_data": {
    "track_status": 1,
    "shipment_status": 7,
    "shipment_track": [
      {
        "id": 236612717, "shipment_id": 236612717, "order_id": 237157589,
        "awb_code": "141123221084922",
        "courier_company_id": 51, "courier_name": "Xpressbees Surface",
        "pickup_date": "2022-07-18 20:28:00",
        "delivered_date": "2022-07-19 11:37:00",
        "weight": "0.30", "packages": 1,
        "current_status": "Delivered", "delivered_to": "Chittoor",
        "origin": "Banglore", "destination": "Chittoor",
        "consignee_name": "", "edd": null,
        "pod": "Available",
        "pod_status": "https://s3-ap-southeast-1.amazonaws.com/kr-shipmultichannel/courier/51/pod/141123221084922.png"
      }
    ],
    "shipment_track_activities": [
      {
        "date": "2022-07-19 11:37:00",
        "status": "DLVD",
        "activity": "Delivered",
        "location": "MADANPALLI, Madanapalli, ANDHRA PRADESH",
        "sr-status": "7",
        "sr-status-label": "DELIVERED"
      }
    ]
  }
}

This is the best tracking response in the Indian carrier set, for three reasons. The scan history is normalised: status is the carrier's own code (which varies by carrier), while sr-status and sr-status-label are Shiprocket's unified code, so you get one status vocabulary across twenty carriers for free. Proof of delivery is a real artefact: pod_status is a URL to a signed or photographed POD image when pod is Available. And weight is the carrier's applied weight, which is what you are billed on.

Courier serviceability and rates

GET /v1/external/courier/serviceability/ with pickup_postcode, delivery_postcode, and either an order_id or both cod and weight. It returns every courier that will carry the shipment with its price and promised date, which is the rate shopping call:

{
  "currency": "INR",
  "data": {
    "available_courier_companies": [
      {
        "courier_company_id": 43,
        "courier_name": "Delhivery Surface",
        "rate": 54, "freight_charge": 54,
        "cod_charges": 0, "cod_multiplier": 0.01,
        "coverage_charges": 0, "other_charges": 0,
        "charge_weight": 0.5, "min_weight": 0.5, "cod": 1,
        "estimated_delivery_days": "4", "etd": "Jul 01, 2024", "etd_hours": 91,
        "cutoff_time": "11:00",
        "pickup_performance": 4.7, "delivery_performance": 5, "rating": 4.9,
        "is_surface": true, "pod_available": "Instant",
        "blocked": 0, "odablock": false
      }
    ]
  }
}

courier_company_id is the value you pass to AWB assignment to force a courier. The collection publishes the full id list; a sample: 1 Blue Dart, 6 DTDC Surface, 10 Delhivery, 14 Ecom Express Surface, 23 Xpressbees 1kg, 43 Delhivery Surface, 48 Ekart Logistics, 51 Xpressbees Surface, 54 Ekart Logistics Surface, 55 Blue Dart Surface, 58 Shadowfax Surface.

GET /v1/external/open/postcode/details resolves a pincode to city and state, and serviceability.shiprocket.in/v1/external/block-pincodes/get returns the pincodes you have blocked yourself.

Returns and cancellations

Returns are first class. GET /v1/external/orders/processing/return lists return orders. They carry their own order_id and shipment_id, their own AWB, and a status in the 21 to 30 band of the status code table above. Exchange orders exist too, created through a separate endpoint.

Cancellation is not a return: POST /v1/external/orders/cancel cancels the order, POST /v1/external/orders/cancel/shipment/awbs cancels shipments by AWB.

NDR

GET /v1/external/ndr/all lists every shipment currently in non delivery, with page, per_page, from, to and search filters. GET /v1/external/ndr/{AWB} returns one.

{
  "data": [
    {
      "id": 94711332,
      "shipment_id": 94323606,
      "awb_code": "784698160933",
      "courier": "FedEx",
      "status": "UNDELIVERED",
      "status_code": 36,
      "reason": "Customer Asked For Future Delivery",
      "attempts": 1,
      "ndr_raised_at": "2021-03-17 22:51:17",
      "escalation_status": "N/A",
      "payment_method": "prepaid",
      "product_name": "Cricket kit",
      "product_price": "1484.01",
      "customer_name": "John Doe",
      "customer_phone": "9999999998",
      "customer_address": "#400, Ground floor, Valley",
      "customer_city": "Gurgaon",
      "customer_pincode": "122003",
      "history": [
        { "id": 50347646, "ndr_id": 14933497, "ndr_reason": "Customer Asked For Future Delivery", "ndr_attempt": 1, "action_by": 3, "comment": "", "proof_image": null, "call_center_call_recording": "" }
      ]
    }
  ]
}

The history[] array with per attempt reasons, call recordings and proof images is unusual and valuable: it is the only place in this set of carriers where you can audit why a delivery failed rather than just that it did.

Payments and settlements

GET /v1/external/account/details/statement returns the wallet ledger with page, per_page, from and to. Each row under data[] carries:

{
  "transaction_id": "", "order_id": "", "channel_order_id": "",
  "awb_code": "", "return_awb_code": null,
  "action": "", "charge": "", "description": "Wallet Balance",
  "debit_amount": "", "credit_amount": "", "balance_amount": "0",
  "applied_weight": "", "charged_weight": "", "billed_weight": "",
  "entered_weight": "", "volumetric_weight": "", "balance_weight": 0,
  "created_at": "", "can_ship": true
}

Every row joins to an awb_code, which makes per shipment cost attribution possible: this is the only carrier connector in this set where you can reconcile freight charged against the shipment that incurred it. The three weight fields (entered_weight, applied_weight, charged_weight, plus billed_weight and volumetric_weight) are the weight dispute audit trail.

GET /v1/external/billing/discrepancy lists open weight discrepancies. GET /v1/external/account/details/wallet-balance returns the current balance, which is worth alarming on, because a zero wallet stops AWB assignment and therefore stops shipping.

Note what is not here: COD remittance. The statement is the freight wallet. Cash collected from customers and remitted to the seller's bank account is a separate cycle, visible in the panel, and not exposed as an endpoint in this collection.

Locations

GET /v1/external/settings/company/pickup returns your warehouses under data.shipping_address[]:

{
  "id": 4984500,
  "pickup_location": "Casa Moderna",
  "name": "CASA MODERNA",
  "address": "1900 GF, Sector 45",
  "address_2": "near Park 1",
  "city": "Gurgaon",
  "state": "Haryana",
  "country": "India",
  "pin_code": "122003",
  "email": "abcxyz@gmail.com",
  "phone": "9667667496",
  "gstin": null,
  "status": 2,
  "phone_verified": 1,
  "lat": "28.44786453607",
  "long": "77.074250297017",
  "rto_address_id": 4984500
}

pickup_location is the human name, and it is the value you send as pickup_location at order creation. id is the numeric key. POST /v1/external/settings/company/addpickup adds one.

Writing back: listings, price and stock

Not applicable in the sales channel sense. Shiprocket is a carrier aggregator, not a marketplace, so there is no listing to price, no MRP to set and no marketplace stock to push. It does hold its own catalogue and stock counters, and those are writable (POST /v1/external/products, POST /v1/external/products/import, PUT /v1/external/inventory/{product_id}/update, POST /v1/external/listings/link to map a channel product to a catalogue product), but nothing written there reaches Amazon, Flipkart or a Shopify storefront. Treat those endpoints as fulfilment configuration, not as a listing writer.

The real write path is the shipment lifecycle, and it is a strict sequence.

1. Create the order

POST /v1/external/orders/create/adhoc for a quick order that does not touch the catalogue, or POST /v1/external/orders/create for a channel specific order.

{
  "order_id": "ORD-10231",
  "order_date": "2026-09-21 11:30",
  "pickup_location": "Casa Moderna",
  "channel_id": "",
  "billing_customer_name": "Ramesh", "billing_last_name": "Kumar",
  "billing_address": "12, MG Road", "billing_city": "Pune",
  "billing_pincode": "411001", "billing_state": "Maharashtra",
  "billing_country": "India", "billing_email": "ramesh@example.com",
  "billing_phone": "9876543210",
  "shipping_is_billing": true,
  "order_items": [
    { "name": "Cotton shirt", "sku": "SHIRT-BLUE-L", "units": 1, "selling_price": "999", "discount": "", "tax": "", "hsn": "610610" }
  ],
  "payment_method": "COD",
  "sub_total": 999, "total_discount": 0,
  "shipping_charges": 0, "giftwrap_charges": 0, "transaction_charges": 0,
  "length": 20, "breadth": 15, "height": 5, "weight": 0.5,
  "invoice_number": "INV-10231", "ewaybill_no": "", "customer_gstin": "",
  "order_type": ""
}

Further fields accepted: billing_address_2, billing_isd_code, billing_alternate_phone, company_name, reseller_name, comment, and the full shipping_* block when shipping_is_billing is false.

{
  "order_id": 16161616,
  "shipment_id": 15151515,
  "status": "NEW",
  "status_code": 1,
  "onboarding_completed_now": 0,
  "awb_code": null,
  "courier_company_id": null,
  "courier_name": null
}

Rules: order_id must be unique within your account and rejects a duplicate with a 422. If shipping_is_billing is false, the whole shipping_* block becomes required. If no channel_id is sent the order lands on the default custom channel. pickup_location must be a registered warehouse name.

2. Assign an AWB

POST /v1/external/courier/assign/awb with {"shipment_id": 16090281, "courier_id": 43}. Omit courier_id to let Shiprocket pick by your rate rules. Send "status": "reassign" to switch courier, which is allowed once per 24 hours per shipment.

{
  "awb_assign_status": 1,
  "response": {
    "data": {
      "awb_code": "321055706540",
      "courier_company_id": 142,
      "courier_name": "Amazon Surface",
      "order_id": 281248157,
      "shipment_id": 16090281,
      "awb_code_status": 1,
      "cod": 0,
      "applied_weight": 0.5,
      "invoice_no": "retail5769122647118",
      "routing_code": "",
      "rto_routing_code": "",
      "pickup_scheduled_date": "2022-11-25 14:00:00",
      "assigned_date_time": { "date": "2022-11-25 11:17:52.878599", "timezone": "Asia/Kolkata", "timezone_type": 3 }
    }
  }
}

A shipped_by object follows, carrying the resolved shipper address (shipper_company_name, shipper_address_1, shipper_city, shipper_state, shipper_postcode, shipper_phone, lat, long) and the RTO address (rto_company_name, rto_city, rto_postcode and the rest). This is the call that debits the wallet.

3. Request pickup

POST /v1/external/courier/generate/pickup with {"shipment_id": [16090109]}. The AWB must already exist. One shipment id at a time despite the array. If the chosen date is a Sunday or a holiday the booking rolls to the next working day.

{
  "pickup_status": 1,
  "response": {
    "pickup_scheduled_date": "2021-12-10 12:39:54",
    "pickup_token_number": "Reference No: 194_BIGFOOT 1966840_11122021",
    "status": 3,
    "pickup_generated_date": { "date": "2021-12-10 12:39:54.034695", "timezone": "Asia/Kolkata", "timezone_type": 3 },
    "data": "Pickup is confirmed by Xpressbees 1kg For AWB :- 143254213727423"
  }
}

4. Label, manifest, invoice

Manifest generation requires the AWB to be assigned and pickup requested, and returns a 400 listing already_manifested_shipment_ids if you repeat it.

The wrapper, which collapses all four

POST /v1/external/shipments/create/forward-shipment takes the order creation body plus mode, request_pickup, print_label and generate_manifest flags and performs the whole sequence in one call. POST /v1/external/shipments/create/return-shipment does the same for returns. For a new connector this is the right starting point; fall back to the four step sequence when you need to choose the courier from the serviceability response.

Cancel

POST /v1/external/orders/cancel/shipment/awbs with {"awbs": ["19041211125783"]}. Up to 2000 AWBs per call, allowed only before the shipment reaches Out for Pickup. It returns 204 with {"message": "Bulk Shipment cancellation is in progress..."}, so cancellation is asynchronous: confirm by re-reading the shipment, not by trusting the response.

Returns and NDR actions

POST /v1/external/orders/create/return creates a reverse pickup, with pickup_* fields carrying the customer address and shipping_* carrying your warehouse, and optional qc_enable and qc_size per item for quality check on doorstep. It returns {"order_id": ..., "shipment_id": ..., "status": "RETURN PENDING", "status_code": 21}.

POST /v1/external/ndr/{awb}/action with {"action": "re-attempt" | "fake-attempt" | "return", "comments": "..."}. Optional phone updates the consignee number on a re-attempt; proof_audio and proof_image URLs are conditionally required when raising a fake attempt dispute. Returns 202 {"status": "Data Updated Sucessfully"}, spelled as shown.

Webhooks and notifications

One webhook, carrying tracking events.

Setup, in the panel, not over the API: Settings, API, Webhooks, add the callback URL, enable the toggle, optionally add a security token.

Contract, as published:

  • Method POST, Content-Type: application/json.
  • The security token, if set, arrives as an x-api-key header. Compare it in constant time and reject anything else. There is no HMAC signature over the body.
  • Your endpoint must return HTTP 200 and nothing else. Shiprocket states the URL should be set to send only code 200 in response.
  • Shiprocket asks that the callback URL not contain the strings shiprocket, kartrocket, sr or kr. Watch out for sr appearing inside an unrelated word in your path.
  • No retry policy is published. Assume at most once delivery and reconcile by polling.

The body is the whole shipment, not a delta, with the full scan list attached:

{
  "awb": "19041424751540",
  "courier_name": "Delhivery Surface",
  "current_status": "IN TRANSIT", "current_status_id": 20,
  "shipment_status": "IN TRANSIT", "shipment_status_id": 18,
  "current_timestamp": "23 05 2023 11:43:52",
  "order_id": "1373900_150876814", "sr_order_id": 348456385,
  "awb_assigned_date": "2023-05-19 11:59:16",
  "pickup_scheduled_date": "2023-05-19 11:59:17",
  "etd": "2023-05-23 15:40:19",
  "is_return": 0, "channel_id": 3422553,
  "pod": "Not Available", "pod_status": "OTP Based Delivery",
  "qc_image": "", "qc_failure_reason": "",
  "scans": [
    {
      "date": "2023-05-19 11:59:16",
      "status": "X-UCI",
      "activity": "Manifested - Manifest uploaded",
      "location": "Chomu_SamodRd_D (Rajasthan)",
      "sr-status": "5",
      "sr-status-label": "MANIFEST GENERATED"
    }
  ]
}

Because the body is the full scan list, the webhook is idempotent by construction: replace the event list for that AWB rather than appending. Note current_timestamp uses a DD MM YYYY HH:MM:SS format with spaces, unlike every other timestamp in the API.

If you are not using webhooks, poll POST /v1/external/courier/track/awbs with a batch of AWBs, and reconcile orders with updated_from/updated_to windows under 30 days.

Rate limits and pagination

Pagination is page and per_page across orders, shipments, NDR, products, listings, inventory and statement. Only the shipments endpoint returns an explicit meta.pagination envelope with total, total_pages and a links.next URL; elsewhere you page until data comes back short.

Rate limits: Shiprocket documents HTTP 429 Too Many Requests as "You have exceeded the API call rate limit" but publishes no numbers in the collection, no per endpoint quota, and no quota headers. Treat this as an unknown. Practical guidance until you have numbers from your account manager: serialise writes, batch tracking reads through POST /courier/track/awbs, keep list pulls to per_page=50 or below, and implement exponential backoff on 429 and on 500 through 504.

Other published limits worth designing around:

Mapping to the unified model

orders

order_items

order_item_id is products[].id, order_id the parent id, channel_sku is products[].channel_sku (and sku on the shipments endpoint), listing_id is products[].product_id (Shiprocket's catalogue id, null when unmapped), title is products[].name, quantity is products[].quantity, status is products[].status. unit_price, tax and discount are accepted at creation but not echoed on the order list, so keep what you sent.

listings and inventory

channel_listing_id maps to the listing id from GET /listings, which is Shiprocket's mirror of a channel listing and exists for mapping, not pricing. sku maps to the product sku. stock maps to the GET /inventory quantity, or products[].available on an order, and is Shiprocket's own count rather than the marketplace's. price, list_price and sale_price have no source: Shiprocket does not hold channel prices.

shipments

Proof of delivery has no unified field yet: keep pod and the pod_status URL in raw.

returns

return_id is the return order's own order_id, because returns are separate orders here. order_id has to be joined back to the forward order through your own channel_order_id: Shiprocket does not link the two in the list response. items[] is order_items[] as sent at creation, status is status_code in the 21 to 30 band, initiated_at is created_at. refund_amount, reason and received_at have no source.

settlements

settlement_id maps to transaction_id, order_id to order_id and channel_order_id, fees[].type to action and description, fees[].amount to debit_amount, net_amount to balance_amount (a running balance, not a per row net) and paid_at to created_at. There is no settlement period object, so period_start and period_end are just the window you queried. This ledger is the freight wallet, not COD remittance. Do not present it as the money owed to the seller.

locations

location_id maps to id, channel_location_id to the pickup_location name string (which is what order creation expects), type is always warehouse, name is name, and address is assembled from address, address_2, city, state and pin_code, with lat and long also available.

Gaps and open questions

  • No published rate limits. A 429 is documented, its threshold is not. Ask your account manager, and build backoff in the meantime.
  • No sandbox. Every integration test spends real money and books real pickups. Plan a disposable seller account or a rigorous cancel-after-create discipline.
  • No COD remittance endpoint. The statement covers the freight wallet. What Shiprocket collected in cash and paid into the seller's bank is panel only. For a D2C brand where most orders are COD, this is the biggest missing piece.
  • The 30 day ceiling on updated_from. You cannot reconstruct change history older than 30 days over the API. Anything older is a creation date sweep or a CSV export, which means the connector must not miss its own window.
  • Timestamp formats are inconsistent. 24 Jul 2019, 11:11 AM on orders, 2022-07-19 11:37:00 on tracking, 23 05 2023 11:43:52 on the webhook, and a nested {date, timezone, timezone_type} object on AWB assignment. All IST, none with an offset. Normalise at the edge.
  • order_id overloading. Three identifiers, similar names, different scopes. This is the top source of real integration defects on this API.
  • Webhook has no signature. An x-api-key you choose is the only defence, and Shiprocket makes it optional.
  • Courier id list drifts. The mapping from courier_company_id to carrier and service is published as static documentation text, not as an endpoint. Re-read the serviceability response rather than hard coding the table. GET /v1/external/courier/courierListWithCounts is the closest thing to a live list.
  • Error envelopes vary. Some failures are {"message": ..., "status_code": ...}, some are {"message": "Oops! Invalid Data.", "errors": {field: [messages]}, "status_code": 422}, and some succeed with HTTP 200 but a zero status flag in the body. Branch on the body, not only on the status line.
  • International, hyperlocal and Shiprocket Fulfillment each have their own endpoint families and their own onboarding (KYC, bank details). This page covers domestic last mile; treat the others as separate integrations.

Sources

Official, fetched on 2026-09-21:

  • Shiprocket API documentation, the published Postman collection: collection SzYW1zB2, owner 8407119, versionTag=latest. The collection JSON was fetched and parsed, and every endpoint, request body, sample response, status code table and courier id quoted above comes from it
  • Collection description sections read in full: Getting Started, Document and API Usage Guidelines, Errors and Response codes, Webhooks (setup, specification and sample body), Sense, Shiprocket MCP Server. Folder descriptions read in full for Authentication API (token lifetime), Couriers (courier id table), Orders (status codes and filters) and Shipments (shipment status codes). Request level documentation and saved example responses read for the twenty endpoints quoted on this page