Shipway is an Indian shipping aggregator and post purchase platform used by D2C brands and marketplace sellers. It bundles carrier aggregation (book an AWB on Delhivery, Blue Dart, XpressBees, Ecom Express, DTDC, Amazon Shipping and others under Shipway's negotiated rates, or under the seller's own carrier accounts), branded tracking pages, NDR workflows and a returns and exchange portal. Unlike most Indian carriers it publishes a complete, public API reference as a Postman collection, which makes it one of the easier Indian logistics integrations to build against.
At a glance
What it is
Shipway (Onjection Labs, Gurugram) started as a tracking and post purchase notification tool and grew into a full shipping aggregator. It is D2C oriented: the typical user is a Shopify, WooCommerce, Magento or marketplace seller who imports orders into Shipway, lets Shipway pick a courier, prints a label, and uses Shipway's tracking page, NDR calling workflow and returns portal as their post purchase stack. A separate product, Shipway Experience, is sold as tracking and notifications only.
Shipway supports two carrier modes and the distinction matters for the connector. A carrier can be a "Shipway" rate card (for example Shipway Bluedart Express (0.5kg)), where Shipway holds the carrier contract, or a carrier the seller connected with their own credentials in Settings, Manage Couriers. The getcarrier response exposes which carriers are aggregator carriers and which support reverse pickup and NDR.
API access
- Self-serve. Register at shipway.com, complete signup, then connect a sales channel in Settings, Integrations and a courier in Settings, Manage Couriers.
- The licence key that doubles as the API password is found in Shipway, Profile, Manage Profile.
- Base host for all documented calls is
https://app.shipway.com/api/. Order push is versioned in the path (v2orders); the remaining endpoints are unversioned. - No official SDK in any language. The docs are a published Postman collection with a "Run in Postman" button, and the raw collection is downloadable from
apidocs.shipway.com/api/collections/14217096/TW74hQ7r. - No published sandbox. The collection description states explicitly that any request made with valid credentials affects real time data in the account.
Authentication
HTTP Basic, with no token exchange and no expiry. One credential pair per Shipway account, so multi seller support means storing one email and licence key per account.
- Take the account email and the licence key from Shipway, Profile, Manage Profile.
- Join them as
email:licence_key. - Base64 encode that string.
- Send it as an
Authorization: Basicheader carrying that value on every request.
TOKEN=$(printf '%s' "seller@example.com:3w2MX1fZl4vm9o3q4g2m0821m0j23147" | base64) curl -X GET "https://app.shipway.com/api/getcarrier" \ -H "Authorization: Basic $TOKEN" \ -H "Content-Type: application/json"
The collection description also mentions passing a token as Authorization: Bearer, which appears to be legacy wording carried over from an older revision. Every worked example in the collection uses Authorization: Basic. Build against Basic and treat the Bearer note as unverified.
Documented status codes: 201 created, 400 missing parameters, 401 authentication failed, 403 forbidden, 404 not found, 405 method not allowed, 500 server error. Note that several endpoints return HTTP 200 with "success": false in the body on a logical failure, so status code alone is not enough: always read success and error.
Objects we can read
Orders
GET https://app.shipway.com/api/getorders
Returns orders in chunks of 100. Filters: orderid, awb_number, tags, date_from and date_to (yyyy-mm-dd), status, shipment_status, new_shipment_status, page.
Order status values: O new order, A processing, E manifested, G dispatched.
Shipment status values: DEL delivered, INT in transit, UND undelivered, RTO, RTD RTO delivered, CAN cancelled, SCH shipment booked, ONH on hold, OOD out for delivery, NFI status pending, NFIDS, plus a reverse set: RSCH pickup scheduled, ROOP out for pickup, RPKP picked up, RDEL return delivered, RINT return in transit, RPSH pickup rescheduled, RCAN return request cancelled, RCLO return request closed, RSMD pickup delayed, PCAN pickup cancelled, ROTH others, RPF pickup failed.
{
"success": 1,
"error": "",
"message": [
{
"order_id": "OD-1245-SS",
"order_total": "870.00",
"shipping_cost": "30.00",
"other_charges": "0.00",
"discount": "0.00",
"payment_id": "12",
"payment_method": "Pre Paid",
"b_firstname": "John",
"b_lastname": "Doe",
"b_address": "#321",
"b_zipcode": "122001",
"s_firstname": "John",
"s_address": "#321",
"s_city": "Gurgaon",
"s_state": "Haryana",
"s_zipcode": "122001",
"s_phone": "9999999999",
"email": "customer@email.com",
"weight": "110",
"box_length": "20",
"box_breadth": "15",
"box_height": "10",
"status": "A",
"invoice_number": "OD-1245-SS"
}
]
}
Billing and shipping name, phone, email and full address are PII.
Pagination is page based (page=1,2,3) at 100 rows per page; there is no cursor and no total count documented, so read until an empty message array. For a delta sync use date_from and date_to plus page.
Order items
Order items are nested inside the order under products, using the same field names as the push request: product, price, product_code, product_quantity, discount, tax_rate, tax_title.
Shipments and tracking
GET https://app.shipway.com/api/tracking?awb_numbers=AWB1,AWB2&tracking_history=1
Up to about 100 AWBs per request (the docs call 100 the recommended maximum). tracking_history=0 or empty returns the current status only; 1 adds the full scan list.
[
{
"awb": 366193892564,
"tracking_details": {
"shipment_status": "SCH",
"shipment_details": [
{
"courier_id": "19339",
"courier_name": "Shipway Amazon Shipping Surface (0.5kg)",
"order_id": "Sho1113T",
"pickup_date": null,
"delivered_date": null,
"weight": "10",
"packages": 1,
"current_status": "Exchange",
"delivered_to": null,
"destination": "Gurgaon",
"consignee_name": "Saksham Aggarwal",
"origin": "Gurugram"
}
],
"track_url": "https://storename.shipway.com/t/366193892564"
}
}
]
An unknown AWB comes back as {"awb": ..., "error": "AWB not found"} inside the same array, so partial failures are per element, not per request.
Returns and cancellations
GET https://app.shipway.com/api/getorders?order_type=Rlists returns using the same filters as orders.GET https://app.shipway.com/api/Getreturnreasonslists return reasons, and the same path with POST creates or updates them. A reason id (return_reason_id) is required when creating a return.GET https://app.shipway.com/api/getrejectreasonslists reject reasons, POST creates and updates them.POST https://app.shipway.com/api/returnorderstatusrejects a return shipment.
NDR
GET https://app.shipway.com/api/ndrlists NDR shipments, withpage,per_page,from,toandsearch(by AWB).GET https://app.shipway.com/api/ndr/{awb}for one shipment, or?awb=AWB1,AWB2,AWB3for several.POST https://app.shipway.com/api/Ndr/OrderDetailsreturns NDR order detail.
{
"data": [
{
"id": 111111,
"shipment_id": "90098765432",
"awb_code": "90098765432",
"customer_name": "Test Customer",
"customer_phone": "8888888888",
"customer_pincode": "654321",
"payment_method": "cod",
"payment_status": "pending",
"status": "Entry Restricted Area",
"status_code": "SHNDR10",
"reason": "Entry Restricted Area",
"attempts": 1,
"ndr_raised_at": "2026-01-01 09:30:00",
"courier": "Test Courier (0.5kg)",
"escalation_status": "open",
"product_name": "Test Combo Product",
"product_price": "499.00",
"history": [
{
"id": 111111,
"ndr_reason": "Entry Restricted Area",
"action_by": "System",
"ndr_attempt": 1,
"medium": "sms",
"ndr_push_status": "success"
}
]
}
]
}
The NDR endpoints are documented with Authorization: Bearer {token} rather than Basic, which is the one place in the collection where the two auth notes actually conflict. Confirm with support before building the NDR read.
Carriers and rates
GET https://app.shipway.com/api/getcarrier, optionally?carrierid=345, returns every carrier enabled on the account withid,name,carrier_title,reverse_status,ndr_statusandaggregator_carrier.GET https://app.shipway.com/api/pincodeserviceable?pincode=122018&payment_type=Preturns the carriers that serve a pincode, one row per carrier and payment type (CCOD,Pprepaid).GET https://app.shipway.com/api/getshipwaycarrierrates?fromPincode=400703&toPincode=400706&paymentType=prepaidreturns a rate card.
{
"success": "success",
"rate_card": [
{
"carrier_id": 7377,
"courier_name": "Shipway Bluedart Express (0.5kg)",
"delivery_charge": 50,
"rto_charge": 50,
"charged_weight": 0.5,
"zone": 1
}
]
}
Locations
GET https://app.shipway.com/api/getwarehouses, optionally ?warehouseid=6430. Returns a map keyed by warehouse id with title, company, contact_person_name, email, phone, address_1, address_2, city, state, country, pincode, latitude, longitude, gst_no, fssai_code, default and status.
Not available
No products or listings, no inventory, no settlements and no COD remittance endpoint. Freight is visible as a rate quote (delivery_charge, rto_charge) and as shipping_cost on the order, but billed weight reconciliation and COD payouts are panel and report functions, not API objects.
Writing back: listings, price and stock
Not applicable, this is a carrier aggregator. Shipway holds no catalogue, price or stock. The write path is the shipment lifecycle.
Push an order
POST https://app.shipway.com/api/v2orders with the order as JSON. Mandatory fields include order_id, products, payment_type (P prepaid, C COD), order_total, the billing and shipping address blocks, warehouse_id and return_warehouse_id. ewaybill is required above Rs 50,000. If carrier_id is omitted, Shipway assigns its recommended carrier.
{
"success": true,
"message": "Order has been added successfully."
}
Book the AWB and label in one call
Send the same request with carrier_id populated and the response carries the waybill and a label PDF URL.
{
"success": true,
"message": "Order has been added successfully.",
"awb_response": {
"success": true,
"message": "AWB No. assigned Successfully",
"AWB": "1333110020164",
"carrier_id": "3411",
"shipping_url": "https://app.shipway.com/shipping_labels/a3359e03e70ae96dd4f97cfe788fa859_thermal.pdf"
}
}
Manifest, pickup, hold, cancel
POST https://app.shipway.com/api/Createmanifest/with{"order_ids": ["TEST123", "test2"]}returns a manifest id.POST https://app.shipway.com/api/createpickup/withpickup_date,pickup_time,office_close_time,package_count,carrier_id,warehouse_id,return_warehouse_id,payment_typeandorder_ids[]. All order ids in one call must share a payment type, belong to the given carrier, and be in Processing or Manifested state.POST https://app.shipway.com/api/Onholdorders/to hold.POST https://app.shipway.com/api/Cancelorders/with{"order_ids": [...]}cancels orders. The response is an array with one result per order id, each with its ownsuccess,errorandmessage.POST https://app.shipway.com/api/Cancel/cancels the shipment (the booked AWB) rather than the order.
Returns
POST https://app.shipway.com/api/Createreturns creates a return, a reverse pickup, or a reverse pickup with quality check, depending on the request. return_order_status selects the flow (E for exchange in the sample), each product carries return_reason_id, optional return_products_images[], customer_notes and variants. The response nests a create_return_response object carrying rma_no.
POST https://app.shipway.com/api/Cancelreturnshipment/ cancels a return shipment.
NDR actions
POST https://app.shipway.com/api/Ndr/ReAttempt requests a reattempt and POST https://app.shipway.com/api/Ndr/RTO pushes the shipment to RTO. POST https://app.shipway.com/api/Ndr/InsertOrder registers an order into the NDR flow.
Warehouses
POST https://app.shipway.com/api/warehouse/ creates a pickup or return location.
Webhooks and notifications
Shipway has one tracking callback. It is configured in the panel, not through an API: log in, go to Track, Tracking Webhooks, add the URL. There is no subscription endpoint and no per event topic selection published.
The callback is a POST with a flat tracking body:
{
"store_code": "1",
"awbno": "12345678901234",
"company_id": "99999",
"pod": "dummy_pod",
"epod": "dummy_epod",
"pickupdate": "2025-01-01 10:00:00",
"expected_delivery_date": "2025-01-05",
"scans_current_status_time": "2025-01-04 15:00:00",
"scans_current_status": "Delivered to consignee",
"carrier": "DummyCarrier",
"api_input": {
"awbno": "12345678901234",
"carrier_id": "99",
"carrier": "DummyCarrier",
"current_status": "DEL",
"current_status_desc": "Delivered",
"received_by": "John Doe",
"country_code": "IN",
"callback_url": "https://dummywebhook.com/callback",
"webhook_sent_date": "2025-01-04 15:30:00",
"scans": {
"0": {
"location": "Dummy_Location_A",
"time": "2025-01-04 15:00:00",
"status": "Delivered to consignee"
}
},
"phone": "9999999999",
"from": "Dummy_Location_A",
"extra_fields": {
"dispatchCount": "1",
"site_url": "https://dummywebsite.com",
"sub_carrier_id": "99",
"StatusType": "DL"
}
}
}
Two things to note. scans is an object keyed by stringified index in newest first order, not a JSON array, so a naive array parser will fail. And proof of delivery arrives inline as pod and epod on the callback rather than through a separate POD endpoint.
No signature, HMAC or shared secret is documented for the callback, and no retry policy or timeout is published. Treat the endpoint as unauthenticated: use an unguessable URL path, and validate every awbno against shipments you actually created before acting on the event.
If webhooks are not used, poll GET /api/tracking with batches of AWBs. A 15 to 30 minute cadence on open shipments is reasonable, plus a getorders sweep on date_from and date_to for anything created or edited outside the connector.
Rate limits and pagination
No rate limits, quota headers or burst allowances are published anywhere in the collection, and no 429 appears in the documented status code table. The only stated volume constraints are structural:
getordersreturns chunks of 100 per page.trackingrecommends a maximum of about 100 AWB numbers per request.- NDR list accepts
pageandper_page.
Pagination models: page number for orders and NDR, batch of identifiers for tracking, date window (date_from, date_to) for delta reads. There is no cursor anywhere, and no updated_at filter, only created date, which means a shipment whose status changed but whose order date is old will not resurface in a date window sweep. Rely on the webhook or on tracking the open AWB set for status changes.
Because limits are unpublished, size the sync conservatively: serialise calls, batch tracking at 100 AWBs, and back off on any 500.
Mapping to the unified model
Gaps and open questions
- Two auth models are described in one collection. The collection header and the tracking endpoint both say Basic; the NDR list endpoint says
Authorization: Bearer {token}and the header text also mentions a Bearer token format. No token issuing endpoint is documented anywhere, which suggests the Bearer wording is stale, but it needs confirming. - No rate limits, quota headers or retry guidance are published.
- The tracking webhook has no signature, secret or retry policy documented. Verification method needs a support conversation before it can be trusted for anything that moves money.
- The unit of
weighton an order is not stated. The sample values ("110"with 20 by 15 by 10 cm dimensions) suggest grams, but this must be confirmed. Getreturnreasonsserves both GET and POST for list, create and update, differentiated only by request body, which makes the contract ambiguous from the collection alone.- No COD remittance, settlement or billed weight dispute endpoint. Whether these exist behind the panel only, or as a report, could not be determined.
- The published Postman collection is the only reference. There is no OpenAPI specification, no changelog and no deprecation policy, so field additions will arrive without notice.
docs.shipway.comanddevelopers.shipway.comboth resolve but serve a "we were unable to find what you were looking for" stub.apidocs.shipway.comis the live portal.