Openleaf

Openleaf shipping API: static bearer token or an email and password getToken call, with rates, order creation, AWB and label, tracking webhook, NDR and B2B POs.

Openleaf is an Indian multi-carrier shipping API that positions itself as the Stripe of logistics: one integration reaching around 60 carriers, quick commerce platforms and hyperlocal partners. It sells to D2C brands and to B2B suppliers shipping against purchase orders raised by quick commerce and modern trade buyers. Its documentation is public, complete and unusually well organised for an Indian logistics vendor: two separate documented API surfaces (B2C and B2B), real request and response samples, a published tracking webhook, and an explicit list of capabilities that are deliberately not exposed.

At a glance

What it is

Openleaf (backed by 100X.VC) sells a unified shipping layer for Indian brands. Its own claims are 60 or more carriers and channels pre-integrated, sub 200 ms API response, one day to go live and a 30 percent NDR recovery lift from an AI agent that calls failed deliveries. Named customers on its site include Mars Cosmetics, Ugaoo, RAS Luxury Oils, Zoff Foods, Salty Jewellery and Elastic Run, split between D2C and B2B quick commerce suppliers.

The important structural fact is that Openleaf runs two distinct API surfaces, on different hosts, with different request shapes:

  • B2C at https://api.openleaf.tech/api/v1/, for forward and reverse parcel shipments against consumer orders.
  • B2B at https://api-b2b.openleaf.tech/api/external/v1/, for booking shipments against purchase orders raised by quick commerce and modern trade buyers, and for reading those purchase orders.

They are independent. Do not assume a field or endpoint from one exists on the other.

For the unified model Openleaf is a source of shipments and, on the B2B side, of orders in the purchase order sense. It holds no catalogue and no stock.

API access

  • Not self-serve. Registration is by contacting contact@openleaf.tech, after which the onboarding team issues the token. The B2C intro page says "Once you register, you will be provided an Authorization Token by the Onboarding Team. Without this token, you will not be authorized to use the API."
  • Versions in force: v1 on both surfaces. The B2B surface is explicitly described as "a stable, curated API surface purpose-built for external integrations", with a note that the documented shapes are exactly what the server accepts today.
  • B2B environments are published: UAT at https://uat-b2b.openleaf.tech, production at https://api-b2b.openleaf.tech, both under /api/external/v1.
  • No official SDK in any language. Openleaf maintains a public Postman workspace, "Openleaf and Unicommerce API", containing staging and production collections, which confirms the same staging and production split.
  • The docs list capabilities deliberately not on the B2B API: uploading order documents (PO copy, ASN, invoice copy), downloading per carton box label PDFs, creating a purchase order directly (POs are raised by Openleaf or by the client's ERP integration), and downloading the generated packing list or shipper sheet spreadsheet for a PO. Each needs a conversation with Openleaf.

Authentication

Two documented routes to the same bearer token.

B2C: exchange email and password for a token

POST https://api.openleaf.tech/api/v1/auth/getToken

curl --location --request POST 'https://api.openleaf.tech/api/v1/auth/getToken' \
  --header 'Content-Type: application/json' \
  --data-raw '{"email": "test_email@gmail.com", "password": "password123"}'
{ "token": "bfa7aeffd55f8df103bd02b228f814e6" }

The token is a 32 character hex string, not a JWT, so nothing can be decoded from it. No expiry, no refresh token and no lifetime is documented, which is consistent with the B2B description of "a single static API token, no login call, no cookie, no session to manage". Treat it as a long lived secret, cache it, and re-fetch on a 401.

B2B: static token only

No login call. Openleaf issues the token directly and rotates it on request if compromised.

Both surfaces present it the same way:

POST /api/external/v1/po/getOrdersBatchwise HTTP/1.1
Host: api-b2b.openleaf.tech
Content-Type: application/json
Authorization: Bearer {your_token}

{ "batch": 0, "filters": {} }

A missing, malformed, invalid or disabled token returns HTTP 401 on the B2B API.

Multi account support means one token per Openleaf account. The token uniquely identifies the account, so there is no per seller scoping inside one token.

Response envelopes differ between the two surfaces

B2B uses one envelope everywhere:

{
  "success": true,
  "status_code": 200,
  "message": "Human readable message",
  "data": { },
  "error": null
}

B2C uses a nested status object plus a result object:

{
  "status": { "message": "Order Placed Successfully", "status_code": 200, "success": true },
  "result": { }
}

Two exceptions on B2C: the rate calculator returns a bare { "rates": [], "custom_courier_rates": [] } with no status wrapper on success, and getAllOrdersData returns a bare data array. Any client has to handle three shapes.

Objects we can read

Orders

GET https://api.openleaf.tech/api/v1/orders/getAllOrdersData

{
  "data": [
    {
      "order_id": "OrderID0001",
      "carrier_name": "Delhivery",
      "payment_mode": "COD",
      "current_order_status": "DELIVERED",
      "awbno": "19534810002520"
    }
  ]
}

This is the only list read on the B2C surface and it is thin: five fields per row, no timestamps, no money, no address. It works as a status sweep, not as a full order sync. Deeper detail has to come from the webhook or from what the connector already stored at creation.

Purchase orders (B2B)

POST /api/external/v1/po/getOrdersBatchwise with {"batch": 0, "filters": {}}, plus documented routes to count purchase orders, fetch one by id, and a published set of purchase order filters. This is the surface that matters for a B2B quick commerce supplier: the PO is the order, raised by the buyer, and the shipment is booked against it.

Shipments and tracking

Status arrives by webhook rather than by a dedicated tracking read on the B2C surface. The B2B surface documents a get order route that returns carrier tracking status alongside the AWB or LR number and the label.

Rates and serviceability

POST https://api.openleaf.tech/api/v1/pricing/rateCalculator

Request: pickup_pincode, destination pincode, weight in grams, a dimensions object in centimetres, payment (PREPAID or COD), and optional invoice_value, city, state, pickup_date (defaults to today) and shipment_type (surface or express, default surface).

{
  "rates": [
    {
      "index": 0,
      "carrier": "delhivery",
      "carrier_name": "Delhivery 2 Kg",
      "cod_charges": 20,
      "price": 91.62,
      "freight_charge": 71.62,
      "mode": 0,
      "zone": "intra_region",
      "courier_company_id": 32,
      "edd": "28-08-2024",
      "pincode": true
    }
  ],
  "custom_courier_rates": []
}

mode is 0 for surface and 1 for express or air. pincode is the boolean serviceability flag, so an unserviceable lane comes back as an empty rates array rather than an error. custom_courier_rates carries carriers the seller added under their own contract, separately from Openleaf's rate card.

Locations

Pickup locations are created over the API (see below) and referenced by name (pickup_location) on every order. The B2B surface documents separate warehouse and customer warehouse lookups, plus a carriers lookup.

Carriers

The B2B docs publish the accepted carrier enum for selected_carrier_options.carrier on non local shipments: delhivery, shiprocket, dtdc, gati, xpressbees, omlogistics, safexpress, fasttrack, dpworld, bluedart, vxpress, quickshift, bigship, gracious, ekart. For a transporter that is not API integrated, the shipment is marked selected_carrier_options.is_local = true instead. On the B2C side, sample carrier values include delhivery and amazon.

Not available

No products or listings, no inventory, no customers endpoint, no settlements and no COD remittance. No proof of delivery endpoint on either surface.

Writing back: listings, price and stock

Not applicable, this is a carrier aggregator. Openleaf holds no catalogue, price or stock. The write path is the shipment lifecycle.

Create a forward order

POST https://api.openleaf.tech/api/v1/order/createOrder

Required: order_id, order_created_time (ISO 8601), pickup_location (the location name, not an id), order_type (COD or PREPAID), order_mode (SURFACE or EXPRESS), the consignee block (customer_name, customer_address_line1, customer_phone, customer_pincode, customer_city, customer_state, customer_country), order_items[] (each with sku, sku_name, sku_mrp, quantity), invoice_value, and a dimensions object (length, breadth, height in cm and weight in grams).

Optional but worth sending: cod_amount, reference_number, invoice_number, marketplace, order_note (printed on the label), and the full GST block (seller_gstin, consignee_gstin, taxable_value, tax_percentage, hsn_code, place_of_supply, ewaybill_serial_number, and per component sgst_tax_rate, sgst_amount, cgst_*, igst_*, gst_total_tax).

{
  "status": { "message": "Order Placed Successfully", "status_code": 200, "success": true },
  "result": {
    "reference_number": "example_ref_no_1234",
    "awb_no": "19534810002520",
    "ol_order_id": "948b6fb1-5e50-40d1-928b-d811d48bc7ab",
    "carrier": "Blue Dart",
    "shipping_label_url": "https://s3.ap-south-1.amazonaws.com/openleaf-order-bucket-03032022-42/shipping_label_77867556712.pdf",
    "validation_key": "560fee51-1af3-4d1d-a6e8-0149b0868d38"
  }
}

The AWB, the selected carrier and the label URL all come back in one call. ol_order_id is Openleaf's own identifier and is what a draft order is later promoted with.

Warning

The docs warn on several endpoints: "in case of any validation failure the order will not get created. Please wait for 10 to 15 seconds before you cancel the request for latency." In other words, do not set a short client timeout and retry: a timed out create may still be in flight. Make creation idempotent on order_id and reconcile before retrying.

Draft orders

POST /api/v1/order/saveDraftOrder saves an order without booking. Sending is_draft: true plus the returned ol_order_id to createOrder promotes the draft into a booked shipment.

Return orders

POST https://api.openleaf.tech/api/v1/order/returnOrder creates a reverse shipment. The request body mirrors createOrder field for field, with the consignee being the pickup point for the return.

Cancel

POST https://api.openleaf.tech/api/v1/order/cancelOrder with {"awb_no": "81037677184"}.

NDR actions

POST https://api.openleaf.tech/api/v1/ndr/reattempt

action is one of reattempt, rto, edit or deferred, despite the route name. Also required: carrier, awbno and a customer_details object with phone and address1. Optional: modified[] naming which fields changed (for example ["phone"]), remarks, and deferred_date (YYYY-MM-DD) for a deferred delivery.

Pickup locations

POST https://api.openleaf.tech/api/v1/pickup/createPickupLocation with warehouse_name, warehouse_manager_name, email, phone, address_line1, city, state, country and pincode. warehouse_name is then the value passed as pickup_location on every order, so names must be unique and stable.

B2B shipment booking

On the B2B surface, the documented flow is create draft order, then save order, then update order, then get order, with cancel order available. selected_carrier_options.carrier takes a value from the published enum, or is_local: true for a transporter Openleaf does not integrate with.

Webhooks and notifications

Openleaf pushes shipment status to the seller's endpoint with a tracking webhook. A POST is sent whenever a relevant tracking event occurs.

{
  "status": "SUCCESS",
  "message": "Shipment tracking data returned successfully",
  "shipmentDetails": {
    "currentStatus": "DELIVERED",
    "current_location": "Thane",
    "current_status_remark": "SHIPMENT DELIVERED ",
    "expected_date_of_delivery": "13-Jan-2025 00:00:00",
    "shipping_provider": "Delhivery",
    "statusDate": "12-Aug-2025 15:48:00",
    "waybill": "37204668339"
  }
}

Points to build around:

  • The endpoint must return HTTP 200. The docs state that any other status code may trigger retries. The retry schedule, count and backoff are not published.
  • There is no signature, HMAC or shared secret documented. The endpoint is bearer-free, so use an unguessable path and validate waybill against known shipments before acting.
  • Timestamps are DD-MMM-YYYY HH:mm:ss, not ISO 8601, and mix concerns: expected_date_of_delivery carries a zeroed time. Parse with an explicit format, do not hand these to a generic date parser.
  • The body carries the current status only, not a scan history. To build shipments.events[] the connector must accumulate webhook deliveries itself.
  • shipmentDetails.currentStatus is the field to key logic on. No published status code table was found, so collect observed values during integration.

If the webhook is not configured, fall back to getAllOrdersData filtered on a date window with status, at a 15 to 30 minute cadence.

Rate limits and pagination

No rate limits, quota headers, burst allowance or 429 semantics are published on either surface. The site advertises sub 200 ms API response times but says nothing about permitted request volume.

Pagination models:

  • B2C orders: page number and page size (page, batchSize), plus a date window and comma separated status, payment mode and carrier filters.
  • B2B purchase orders: batch based ({"batch": 0, "filters": {}}), with a separate count endpoint to size the run.
  • Everything else is per identifier or per lane, with no pagination.

The documented latency warning ("wait 10 to 15 seconds before you cancel the request") is the practical constraint on concurrency: creation calls can take seconds because they go out to a carrier. Size client timeouts above that, keep concurrency modest, and never auto retry a create on timeout.

Mapping to the unified model

For B2B, map the purchase order to orders with channel_order_id as the PO number, and the booked shipment's AWB or LR number to shipments.awb.

Gaps and open questions

  • Token lifetime and rotation are not documented on the B2C surface. The B2B docs describe a static token rotated on request; whether the B2C getToken result expires is unstated.
  • No signature or shared secret on the tracking webhook, and no published retry policy, retry count or backoff.
  • No status code vocabulary is published for currentStatus or current_order_status, so the status mapping has to be built from observation.
  • No rate limits anywhere.
  • getAllOrdersData returns only five fields. Whether a fuller order detail read exists is unclear, which makes backfilling historical orders from Openleaf alone impractical.
  • No proof of delivery and no COD remittance or settlement endpoint on either surface.
  • The three different response envelopes (B2B standard, B2C status plus result, and bare bodies on the rate calculator and order list) are a client complexity tax and suggest the surfaces grew separately.
  • Published samples contain small JSON errors (a missing comma after "cod_charges": 20 in the rate response sample), so the samples are hand maintained rather than generated from a specification. Field names should be confirmed against a live response.
  • No sandbox is documented for the B2C API, only for B2B.
  • The B2B docs name four capabilities available only by asking Openleaf: document upload, per carton box label PDFs, direct PO creation and packing list download.

Sources