LSP DMS Gateway — API Integration
How your DMS integrates with Aarambh's logistics gateway. Two directions: you call the gateway to register riders and report order lifecycle events, and the gateway calls your webhook / base URL to push order create / cancel / update and to read order status & rider position. Aarambh's own internal services are out of scope for this document — you only ever talk to the gateway URL and receive calls on your published endpoints.
Overview
subscriberid + x-access-key / x-secret-key
Your DMS ───────────────────────────────────────────▶ Aarambh LSP DMS Gateway
▲ (agent sync, order status & events)
│ ar-access-key / ar-secret-key ({{WEBHOOK_URI}})
│◀────────────────────────────── order/create · order/cancel · order/update
│◀────────────────────────────── rider/allocate · rider/payment
│◀────────────────────────────── order/status/get · agent/position (reads)
│
│ Authorization: bearer ({{BASE_URI}})
│◀────────────────────────────── order/getNearest · order/request_callback- Inbound (you → gateway): agent create / update / live-location, agent session tokens for the SDK (
agent/acquireSession), order status update, order status cancel, order events (PCC/DCC confirmation, RTO verification, callback request), a read of an order in theorder/createshape, a notification relay (notification/create), and payment reads — order earning, rider earning and rider incentive (payment/*). - Outbound (gateway → you): the gateway pushes
order/create,order/cancel,order/update,rider/allocateandrider/paymentto your{{WEBHOOK_URI}}, and readsorder/status/getandagent/positionfrom it. It also callsorder/getNearestandorder/request_callbackon your{{BASE_URI}}.
Every gateway route is under the /lsp-dms-gateway prefix — e.g. the
create-agent route is POST /lsp-dms-gateway/v1/agent/create. Paths below omit the prefix.
Authentication
| Direction | Scheme | Headers |
|---|---|---|
| You → gateway (every route except health / docs) | Subscriber key pair, issued to you per subscriber | subscriberid, x-access-key, x-secret-key |
Gateway → your {{WEBHOOK_URI}} (order/create, order/cancel, order/update, rider/allocate, rider/payment, order/status/get, agent/position) | Static key pair you provision for Aarambh | ar-access-key, ar-secret-key |
Gateway → your {{BASE_URI}} (order/getNearest, order/request_callback) | Static bearer you provision for Aarambh | Authorization |
x-access-key / x-secret-key authenticate your backend to the gateway and must
never ship inside a mobile app or browser bundle.
- Missing
subscriberid→400 { "error": "subscriberid header is required" } - Missing / wrong
x-access-key/x-secret-key→401
Conventions
- Success:
200 { "message": "<text>", "data": <payload> }. - Error:
{ "error": "<message>" }with the HTTP status. When an upstream call to your endpoints fails, the response addspartnerStatusandpartnerResponse. - Coordinates (agent endpoints):
["<lat>", "<long>"]— strings, each with exactly 6 digits after the decimal point (-?\d+\.\d{6}). userCode: the agent code returned byagent/create(e.g.sck0005). Use it on every later agent / order call — not a code of your own.subscriberid: your subscriber identifier; every record is scoped by it.
Agent APIs
You call these to register and sync your riders.
POST/v1/agent/create
Registers a rider. Returns a server-generated userCode.
{
"currentLocation": ["12.912345", "77.612345"],
"mobile": "9845405219",
"name": "Shashank Rao",
"isActive": true,
"timestamp": "2026-08-31T06:19:09.467Z",
"vehicle": { "type": "bike", "brand": "Hero", "number": "KA01AB1234", "model": "Splendor" },
"notification": { "provider": "samsung", "token": "<fcm-token>", "enabled": true },
"device": {
"id": "string", "osVersion": "14", "appVersion": "1.2.3",
"isCharging": true, "batteryLevel": 100, "isVirtual": false,
"languageCode": "en", "languageTag": "en-IN",
"manufacturer": "samsung", "model": "SM-S911B", "permissions": {}
},
// optional profile fields
"firstName": "shashank",
"lastName": "rao",
"profilePic": "",
"gender": "male",
"addressDetails": {
"location": { "type": "Point", "coordinates": [77.5946, 12.9716] },
"pincode": "560001",
"city": "Bangalore"
},
"verified": true,
"enabled": 1
}Validation (400 on first failure)
| Field | Rule |
|---|---|
currentLocation | required; array of exactly 2 strings, each -?\d+\.\d{6} |
mobile | required; string of exactly 10 digits |
name | required; non-empty string |
notification | optional. provider, if present, one of huawei, xiaomi, oppo, vivo, honor, meizu, oneplus, realme, iqoo, google, samsung. When enabled === true: provider and token required, and device mandatory with device.manufacturer === notification.provider. |
device | optional. device.manufacturer, if present, restricted to the provider enum. |
isActive | optional boolean |
firstName, lastName | optional strings. Default: name split at the first space. |
profilePic, gender | optional strings. Default: "". |
addressDetails | optional. When sent, location.coordinates is required: two numbers in GeoJSON [long, lat] order (unlike currentLocation's [lat, long] strings). location.type, if sent, must be "Point". pincode, building, locality, city, state, country are optional strings. |
verified | optional; must be a real boolean. Default: true. |
enabled | optional; one of 0 (disabled), 1 (enabled), 2 (blocked). Default: 1. |
Notes
- A second create for the same
mobileis rejected (400). userCodeis generated by Aarambh — do not send your own.notificationis stored against the rider's user record;deviceis stored as a device record linked to the rider.
{ "message": "Agent created successfully",
"data": { "userId": "...", "agentId": "...", "userCode": "sck0005", "vendorCode": "<subscriberid>" } }POST/v1/agent/update
{
"UserCode": "sck0005",
"timestamp": "2026-08-31T06:20:00Z",
"isActive": true,
"vehicle": { "brand": "Hero", "number": "KA01AB1234" },
"notification": { "provider": "samsung", "token": "<fcm>", "enabled": true },
"device": { "manufacturer": "samsung", "model": "SM-S911B" }
}| Field | Rule |
|---|---|
UserCode | required; non-empty string |
isActive | optional; must be a real boolean — "true"/"false" strings and numbers are rejected 400 |
notification, device | same rules as agent/create |
isActive vs an active taskisActive: true marks the rider online and available; isActive: false
marks them offline and unavailable. But if the rider is currently on an active task
(Agent-assigned … Out-for-delivery, or RTO-Initiated),
isActive is ignored (even false) — only
vehicle / notification / device are applied. The call still returns
200.
POST/v1/agent/live-location
A GPS ping for a rider. Location only — it does not change online / available state.
Rate-limited per subscriber → 429 with a Retry-After header.
{ "UserCode": "sck0005", "currentLocation": ["12.912345", "77.612345"] }UserCode required non-empty; currentLocation required, same 2-element / 6-decimal rule.
POST/v1/agent/acquireSession
Issues a session token for a rider created with agent/create. This is the token your app
passes to the Aarambh SDK's login() — see the SDK integration guide.
Call it whenever the app needs a fresh token (e.g. on each login), not just once at registration.
{
"UserCode": "sck0005",
"notifyToken": "<FCM device token for push notifications>"
}| Field | Rule |
|---|---|
UserCode | required; non-empty string — the userCode returned by agent/create |
notifyToken | optional string. When sent, replaces the rider's stored push token; when omitted, the stored one is kept. |
Each call issues a new token (valid 30 days) and invalidates the rider's previous one — an app still holding the old token is signed out on its next call.
{ "message": "session acquired successfully",
"data": {
"token": "<session token>",
"agentDetails": { "_id": "...", "agentId": "...", "isAvailable": true, "role": "Agent" },
"currentUser": {
"_id": "...", "name": "Shashank Rao", "email": "", "role": "Agent",
"userCode": "sck0005", "deviceId": "...", "enabled": 1, "isAccountLocked": false
},
"issued_at": "2026-09-23T06:19:09.467Z"
} }Unknown UserCode for your subscriber → 404.
GET/v1/agent/{userCode}/location
Reads the rider's current position from your {{WEBHOOK_URI}}/agent/position?userCode=<userCode>
and returns your response unchanged.
Order APIs
You call these to report order lifecycle events.
POST/v1/order/status/update
{
"order_id": "ORD-1788156784513-12|bdtest",
"transaction_id": "TXN-1788156784513-12|bdtest",
"status": "Order-picked-up",
"assignee": "sck0005",
"timestamp": "2026-08-31T07:00:00Z",
"call_back_support": "no",
"proof": { "type": "OTP", "code": "8309", "image": "https://.../pod.jpg", "gps": "12.912345,77.612345" }
}| Field | Rule |
|---|---|
order_id, transaction_id | required, non-empty. Both must identify the same order for your subscriber. |
status | required. Verbatim, case-sensitive, one of Agent-assigned, At-pickup, Order-picked-up, Out-for-delivery, At-delivery, Order-delivered, RTO-Initiated, RTO-Delivered, Cancelled. |
assignee | required; the rider's userCode. Must be a rider you registered — else 404 INVALID_USERCODE. (Rider name / mobile are not needed.) |
call_back_support | optional; "yes" / "no" |
proof | optional. proof.type ∈ SCAN,IMAGE,OTP,NO_AUTH; proof.gps = "lat,lng". Mandatory (proof.image or proof.code) for Order-picked-up and Order-delivered. |
What the gateway checks (each stop → the noted status, nothing is applied)
- The
assigneeresolves to one of your riders — else404 INVALID_USERCODE. - The order exists for your subscriber — else
404. - The order isn't already finished (
Order-delivered/RTO-Delivered/RTO-Disposed/Cancelled) — if it is, you get200 { "skipped": true }and nothing changes. - Strictly one step forward along
Agent-assigned → At-pickup → Order-picked-up → Out-for-delivery → At-delivery → Order-delivered— see the rules section. Backwards →409 STATUS_REGRESSION; skipping a step →409 NON_INCREMENTAL_STATUS; the same status again →200no-op. - Confirmation gate: when the order requires a pickup / delivery code, that code must have been confirmed first (via order/event) — else
409 PCC_NOT_VERIFIED/409 DCC_NOT_VERIFIED(FIFO pickup:409 PCC_SCAN_REQUIRED).RTO-Deliveredsimilarly needs anrto-verificationevent when the order carries an RTO code →409 RTO_NOT_VERIFIED. - Idempotent: repeating the same
(order_id, status)within a short window returns200 { "idempotent": true }and is not re-processed. A failed attempt clears the window so you can retry.
Behavior by status
status | Effect |
|---|---|
Agent-assigned … Order-delivered | advances the order one forward step (strictly incremental). |
RTO-Initiated | starts a return. Whether it becomes a return-to-origin or a disposal depends on the order's RTO configuration; the buyer platform is notified. |
RTO-Delivered | completes a return. Requires the order to currently be RTO-Initiated; the buyer platform is notified. |
Cancelled | cancels the order; the buyer platform is notified. |
On success the response is 200. Terminal / same-rank calls return
{ "skipped": true, ... }; repeats return { "idempotent": true, ... }.
POST/v1/order/status/cancel
{
"order_id": "ORD-...",
"transaction_id": "TXN-...",
"bap_id": "protocol.lbnp.preprod.aarambh.cloud",
"cancellation_reason_id": "013",
"cancellation_reason": "Customer unreachable",
"is_rto": false
}order_id,transaction_id— required, non-empty.is_rto— optional; strict boolean.trueroutes the cancellation as a return.cancellation_reason_id— optional; an ONDC reason code, used as-is (falls back to"013").
The order is cancelled and the buyer platform is notified. If the order was already
past pickup, the cancellation is treated as a return (initiated or disposed, per the order's RTO
configuration). Terminal orders return 200 with no change; repeats are idempotent.
POST/v1/order/event
{ "order_id": "ORD-...", "transaction_id": "TXN-...",
"events": { "type": "otp", "value": "8309" } }events.type | Behavior |
|---|---|
otp / scan | Pickup / delivery confirmation. Which one is inferred from the order's current status (at/before pickup → pickup code; after → delivery code). events.value must exactly match the order's pickup / delivery code. For a FIFO order the pickup confirmation must be sent as scan; otherwise otp. On a match the confirmation is recorded and unblocks Order-picked-up / Order-delivered. Mismatch → 409 <KIND>_MISMATCH; wrong event type → 400; the leg has no code → 400. |
rto-verification | Return verification. Only valid when the order carries an RTO code; events.value must exactly match it. On a match it unblocks RTO-Delivered. Mismatch → 409 RTO_MISMATCH; not required → 400. |
callbackrequest(also callback_request, callback) | Callback request. Raises a support-callback for the order. Returns { "requestSupport": true }. |
| anything else | Recorded against the order; no other effect. |
POST/v1/order/nearest
Passes through to your {{BASE_URI}}/order/getNearest. Body
{ "coordinates": ["<lat>","<lng>"], "limit": 5, "radius": 5 }; coordinates required.
GET/v1/order/getOrder/{id}
Returns the order (by order_id, or task_id) in the same shape as the
order/create webhook payload. 404 if the order isn't found for your subscriber.
{
"order_id": "ORD-...",
"pickupProof": { "type": "OTP", "enabled": true, "image": true, "gps_enabled": true, "verification_type": "AARAMBH" },
"dropProof": { /* same shape */ },
"rtoProof": { /* drop shape, enabled when the order is RTO-configured */ },
"start": { "location": { "gps": "13.026000,77.593500", "address": { ... } }, "contact": { "phone": "...", "email": "..." } },
"end": { "location": { "gps": "12.908300,77.605000", "address": { ... }, "contact": { ... } } },
"quote": { "price": "267.03", "breakup": [ { "type": "delivery", "price": "226.30" }, { "type": "tax", "price": "40.73" } ] },
"payment": { "type": "ON-ORDER", "amount": "" },
"merchant_order_id": "bdtest|1788156784513",
"retail_item_details": [ { "name": "...", "quantity": "2", "price": "3" } ],
"parcel_type": "P3",
"support": { "type": "", "contact": "" },
"groupable": false,
"sla": { "pickup": "PT15M", "delivery": "PT60M", "cancellation": "" },
"rto_enabled": true,
"status": "Agent-assigned"
}| Field | How it's set |
|---|---|
pickupProof / dropProof | enabled: true + type: OTP only when that leg has a confirmation code; otherwise enabled: false, type: NO_AUTH. image = whether a photo is expected; gps_enabled = whether tracking is on. |
verification_type | "AARAMBH" when the proof is enabled, else "". |
rtoProof / rto_enabled | enabled when the order is configured for return-to-origin. |
parcel_type | weight-banded — ≤1kg P1, ≤5kg P2, ≤15kg P3, else P4. |
sla | pickup / delivery durations from the order; cancellation is "". |
Notification APIs
You call this to have the gateway relay a push notification to your own
{{WEBHOOK_URI}}.
POST/v1/notification/create
Forwards the request body as-is to {{WEBHOOK_URI}}/notification/push
(ar-access-key / ar-secret-key) and returns your webhook's response back to
you, wrapped in the standard success envelope.
{
"order_id": "ORD-1788156784513-12|bdtest",
"transaction_id": "TXN-1788156784513-12|bdtest",
// ...any other fields your {{WEBHOOK_URI}}/notification/push endpoint expects
}Only the subscriberid / x-access-key / x-secret-key headers are
checked; the body itself is not validated by the gateway — it is relayed
verbatim to your webhook, which is free to define its own contract for it.
order_id— optional, but when present the call is also recorded as anotificationCreateevent against that order's sync history (created if it doesn't already exist for your subscriber).transaction_id— optional; stored alongside the sync record whenorder_idis present.
{ "message": "Notification created successfully",
"data": /* whatever your {{WEBHOOK_URI}}/notification/push endpoint returned */ }If the call to your {{WEBHOOK_URI}}/notification/push fails, you get
502 with partnerStatus / partnerResponse — same as any other
upstream call the gateway makes on your behalf.
Payment APIs
Read-only. You call these to fetch what a rider earned — per order, per rider over a date range, and the incentives a rider unlocked over a date range. Figures are the ones Aarambh settled for the order; nothing is recalculated on read.
- Dates:
fromandtoareYYYY-MM-DD, both required and both inclusive. A range can span at most 31 days. - Day boundary: a day is the operational day, 06:00 IST → 06:00 IST — an order settled at 02:00 on the 2nd belongs to the 1st.
- Rider: identified by the
userCodereturned fromagent/create.
GET/v1/payment/order_earning/{orderId}
The earning for one order: the rider who completed it, the amount, and its breakup.
404 if no earning has been settled for that order_id under your subscriber.
{ "message": "Order earning fetched successfully",
"data": {
"order_id": "ORD-1788156784513-12|bdtest",
"user_code": "sck0005",
"amount": 120,
"breakup": { "base_price": 40, "last_mile_charges": 30, "first_mile_charges": 10, "surge": 20, "rto_earning": 0, "rain_charge": 20, "adjustment": 0 },
"first_mile_distance": 1.2,
"last_mile_distance": 4.5,
"rto_distance": 0,
"type": "system_calculated",
"earned_at": "2026-09-01T05:00:00.000Z"
} }| Field | Meaning |
|---|---|
user_code | The rider the earning was settled to — the one who completed the order. |
amount | The rider's total earning for the order. |
breakup.base_price | Base fare. |
breakup.last_mile_charges | Distance charge for the pickup → drop leg, beyond what the base fare covers. |
breakup.first_mile_charges | Charge for the rider's travel to the pickup point. |
breakup.surge | Peak / time-slot surge. |
breakup.rto_earning | Earning for the return leg, when the order went RTO. |
breakup.rain_charge | Rain surge. |
breakup.adjustment | amount minus the components above, so the breakup always adds up to amount. 0 unless the amount was manually corrected. |
first_mile_distance / last_mile_distance / rto_distance | Distances in km for each leg. These are distances, not money. |
type | system_calculated, or manual_update when an operator corrected the amount. |
earned_at | When the earning was settled (UTC). |
GET/v1/payment/rider_earning/{userCode}?from=&to=
A rider's earnings over a date range: the total, and one entry per order in the same shape as
order_earning (each with its own breakup). A rider with no earnings in the
range returns amount: 0 and an empty orders list.
GET /v1/payment/rider_earning/sck0005?from=2026-09-01&to=2026-09-07{ "message": "Rider earning fetched successfully",
"data": {
"user_code": "sck0005",
"from": "2026-09-01",
"to": "2026-09-07",
"amount": 180, // sum of orders[].amount
"total_orders": 2,
"orders": [
{
"order_id": "ORD-1788156784513-12|bdtest",
"amount": 120,
"breakup": { "base_price": 40, "last_mile_charges": 30, "first_mile_charges": 10, "surge": 20, "rto_earning": 0, "rain_charge": 20, "adjustment": 0 },
"first_mile_distance": 1.2,
"last_mile_distance": 4.5,
"rto_distance": 0,
"type": "system_calculated",
"earned_at": "2026-09-01T05:00:00.000Z"
},
{ "order_id": "ORD-1788156799001-04|bdtest", "amount": 60, "breakup": { /* same shape */ }, ... }
]
} }GET/v1/payment/rider_incentive/{userCode}?from=&to=
The incentives a rider unlocked over a date range: the total, its split by incentive type, and per
incentive cycle the slabs that were unlocked. A rider with nothing unlocked in the range returns
total_incentive: 0 and an empty incentives list.
GET /v1/payment/rider_incentive/sck0005?from=2026-09-01&to=2026-09-07{ "message": "Rider incentive fetched successfully",
"data": {
"user_code": "sck0005",
"from": "2026-09-01",
"to": "2026-09-07",
"total_incentive": 350,
"breakup": { "daily": 50, "weekly": 300, "festival": 0 },
"incentives": [
{
"incentive_id": "7c1f...",
"name": "Weekly booster",
"type": "WEEKLY",
"incentive_on": "ordercount",
"period": "2026-W36",
"total_orders": 42,
"total_earnings": 3000,
"amount": 300,
"slabs": [
{ "slab_count": 20, "slab_amount": 100, "amount": 100, "unlocked_at": "2026-09-01T06:30:00.000Z" },
{ "slab_count": 40, "slab_amount": 300, "amount": 200, "unlocked_at": "2026-09-04T06:30:00.000Z" }
]
},
{ "name": "Daily 10", "type": "DAILY", "period": "2026-09-04", "amount": 50, ... }
]
} }| Field | Meaning |
|---|---|
total_incentive | Incentive credited to the rider within the range — the sum of incentives[].amount. |
breakup | total_incentive split by incentive type: daily, weekly, festival. |
type / period | The incentive's cycle. period is YYYY-MM-DD for DAILY / FESTIVAL and YYYY-Www (ISO week) for WEEKLY. |
incentive_on | What the slabs count: ordercount (orders delivered) or earning (order earnings). |
total_orders / total_earnings | The rider's progress in that cycle. |
slabs[].slab_count / slab_amount | The slab's target and its configured payout. |
slabs[].amount | What unlocking that slab added. Slabs are tiers — a cycle pays its highest unlocked slab, not the sum — so each slab is credited only its increment over the previous one. |
slabs[].unlocked_at | When the slab was unlocked (UTC). Only slabs unlocked inside the range are listed. |
Because each slab carries only its increment, querying two adjacent ranges and adding the totals gives the same figure as querying the combined range — a weekly incentive that crosses your range boundary is never counted twice.
Maps APIs
Road routing, snapping and reachability over Aarambh's own map server (GraphHopper 12.0,
OpenStreetMap data for India). This is a separate service from the gateway: its own base URI, its own key,
and GraphHopper's request / response shapes — not the gateway's { message, data } envelope.
| Base URI | https://api-maps.aarambh.cloud |
| Auth | Your maps key, as the key query param or the X-API-Key header. Issued separately from the gateway key pair. |
| Coverage | India — bbox [68.262955, 6.756888, 97.329353, 35.568624] (min lon, min lat, max lon, max lat). Points outside → 400. |
| Profiles | car only. Any other profile → 400. |
| CORS | Access-Control-Allow-Origin: *, methods GET, POST, HEAD, OPTIONS. |
- Coordinate order differs by format. The
pointquery param islat,lon(point=12.9716,77.5946). Every JSON / GeoJSON body and response — POSTpoints,coordinates,bbox— is[lon, lat]. - Units:
distancein metres,timein milliseconds. - Polylines:
points_encodeddefaults totrue(Google polyline, multiplier1e5). Passpoints_encoded=falsefor a GeoJSONLineString.
A missing or wrong key returns the proxy's 404 Not Found HTML page, not a JSON error.
If every call 404s, check the key before the path.
The key is billed and rate-limited per subscriber. Call the maps server from your backend, or proxy it, rather than shipping the key in a web page anyone can read.
GET/route
Driving route through two or more points, in order: distance, ETA, geometry and optional turn-by-turn.
GET https://api-maps.aarambh.cloud/route?point=12.9716,77.5946&point=12.9352,77.6245&profile=car&points_encoded=false&key={{MAPS_API_KEY}}| Param | Default | Meaning |
|---|---|---|
point | — | Required, repeat 2+ times: lat,lon. Start, any via points, destination — visited in the order given. |
profile | — | car. |
points_encoded | true | false → geometry as GeoJSON LineString. |
calc_points | true | false → distance / time only, no geometry. Fastest option for ETA-only lookups. |
instructions | true | Turn-by-turn list. false to drop it. |
locale | en | Instruction language, e.g. hi, en. |
details | — | Repeatable. Per-segment attributes along the path, e.g. road_class, max_speed, road_environment, toll, distance, time, street_name. |
type | json | gpx → the route as a GPX file. |
snap_prevention | — | Repeatable. Don't snap points onto motorway, trunk, ferry, tunnel, bridge or ford. |
curbside | — | Repeatable, one per point: any, left, right — arrive with the point on that side. |
ch.disable | false | true switches to flexible mode. Required for heading, algorithm, custom_model and curbside. Slower than the default. |
heading | — | Repeatable, one per point: direction of travel in degrees (0 = north, clockwise). Avoids routes that start with a U-turn for a moving rider. |
algorithm | — | alternative_route (with alternative_route.max_paths, default 2) or round_trip (with round_trip.distance in m and round_trip.seed; takes one point). |
{ "info": { "copyrights": ["GraphHopper", "OpenStreetMap contributors"], "took": 11, "road_data_timestamp": "2026-04-24T20:20:51Z" },
"paths": [ {
"distance": 6579.883, // metres
"time": 263196, // milliseconds
"bbox": [77.594568, 12.934965, 77.624481, 12.971707],
"points_encoded": false,
"points": { "type": "LineString", "coordinates": [[77.594568, 12.971606], [77.594588, 12.971707], /* … */] },
"snapped_waypoints": { "type": "LineString", "coordinates": [[77.594568, 12.971606], [77.624481, 12.935222]] },
"instructions": [
{ "text": "Continue", "sign": 0, "distance": 58.02, "time": 2321, "interval": [0, 3], "street_name": "" },
{ "text": "Turn left", "sign": -2, "distance": 13.486, "time": 539, "interval": [3, 5], "street_name": "" }
/* … */
],
"details": { "road_class": [[0, 2, "footway"], [2, 5, "service"] /* … */] }
} ] }| Field | Meaning |
|---|---|
paths[] | One route; several with algorithm=alternative_route, best first. |
snapped_waypoints | Where each input point landed on the road network, in input order. |
instructions[].sign | Manoeuvre: -3 sharp left, -2 left, -1 slight left, 0 continue, 1 slight right, 2 right, 3 sharp right, 4 finish, 5 via point reached, 6 roundabout, -98 U-turn. |
instructions[].interval / details.*[i] | Index range [from, to] into the path's coordinate list. A detail entry is [from, to, value]. |
info.road_data_timestamp | Date of the OSM snapshot the graph was built from. |
POST/route
Same as GET, with the request as a JSON body. Use it for many points, and for custom_model,
which only exists in POST. Note points is [lon, lat].
POST https://api-maps.aarambh.cloud/route
X-API-Key: {{MAPS_API_KEY}}
Content-Type: application/json
{
"points": [[77.5946, 12.9716], [77.6245, 12.9352]],
"profile": "car",
"points_encoded": false,
"instructions": false,
"details": ["road_class"],
"ch.disable": true,
"custom_model": {
"speed": [{ "if": "road_class == MOTORWAY", "limit_to": "60" }],
"priority": [{ "if": "road_class == TRACK", "multiply_by": "0" }]
}
}- Every GET param maps to a body field of the same name; repeatable params become arrays (
details,headings,curbsides,snap_preventions). custom_modelrules:speed(limit_tokm/h ormultiply_by),priority(multiply_by;0forbids the road), andareas(GeoJSON polygons to reference asin_<area_id>). Conditions can use the encoded values listed by/info, e.g.road_class,road_environment,max_speed,roundabout,country.- A
custom_modelneeds"ch.disable": true.
POST/match?profile=car
Map matching: snaps a recorded GPS trace onto roads. Use it to compute the distance a rider actually drove
from their location pings. The response has the same shape as /route, plus a map_matching summary.
POST https://api-maps.aarambh.cloud/match?profile=car&points_encoded=false&gps_accuracy=40&key={{MAPS_API_KEY}}
Content-Type: application/gpx+xml
<gpx version="1.1" creator="dms"><trk><trkseg>
<trkpt lat="12.9716" lon="77.5946"><time>2026-10-05T10:00:00Z</time></trkpt>
<trkpt lat="12.9690" lon="77.5990"><time>2026-10-05T10:01:00Z</time></trkpt>
<trkpt lat="12.9650" lon="77.6040"><time>2026-10-05T10:02:00Z</time></trkpt>
</trkseg></trk></gpx>{ "map_matching": { "original_distance": 1258.56, "distance": 1689.691, "time": 67585 },
"paths": [ { "distance": 1689.691, "time": 67585, "points": { /* matched LineString */ }, /* … as /route */ } ] }gps_accuracy(m, default40): how far a ping may sit from the road. Raise it for noisy traces.original_distanceis the straight-line sum between pings;distanceis the matched on-road distance.- Accepts the same output params as
/route(points_encoded,instructions,details,type=gpx).
GET/isochrone
The area reachable from a point within a time or distance limit, as GeoJSON polygons — e.g. "everywhere a rider can reach in 10 minutes", for serviceability zones.
GET https://api-maps.aarambh.cloud/isochrone?point=12.9716,77.5946&profile=car&time_limit=600&buckets=2&key={{MAPS_API_KEY}}| Param | Default | Meaning |
|---|---|---|
point | — | Required, lat,lon. |
time_limit | 600 | Seconds. |
distance_limit | — | Metres. Set instead of time_limit. |
buckets | 1 | Split the limit into N equal rings, one polygon each (properties.bucket = 0…N-1, innermost first). |
reverse_flow | false | true → the area from which the point can be reached (inbound), e.g. riders who can get to a pickup in time. |
{ "polygons": [
{ "type": "Feature", "properties": { "bucket": 0 },
"geometry": { "type": "Polygon", "coordinates": [[[77.470879, 12.988645], [77.471762, 12.988897], /* … */]] } }
],
"info": { "took": 556, /* … */ } }Isochrones are the slowest call — a 10-minute limit in a dense city can take several seconds. Cache results rather than computing one per request.
GET/spt
Shortest-path tree: every road node reachable within the limit, with its travel time and distance from the
point. Returned as CSV. Same point, time_limit, distance_limit and
reverse_flow params as /isochrone.
GET https://api-maps.aarambh.cloud/spt?point=12.9716,77.5946&profile=car&distance_limit=500&columns=longitude,latitude,time,distance,prev_longitude,prev_latitude,edge_id&key={{MAPS_API_KEY}}longitude,latitude,time,distance,prev_longitude,prev_latitude,edge_id
77.594568,12.971606,0,0,null,null,-1
77.594714,12.971656,1045,26,77.594568,12.971606,24160304
…columnsdefaults tolongitude,latitude,time,distance. Also available:prev_longitude,prev_latitude,prev_time,prev_distance,edge_id,base_node,adj_node, and any encoded value from/info(e.g.road_class).timeis milliseconds,distancemetres.
GET/nearest
Snaps one coordinate to the closest point on the road network. Use it to check a pickup / drop pin is on a reachable road, or to clean up a rider's raw GPS position.
GET https://api-maps.aarambh.cloud/nearest?point=12.9716,77.5946&profile=car&key={{MAPS_API_KEY}}{ "type": "Point", "coordinates": [77.594567, 12.971606], "distance": 3.57 } // snapped [lon, lat]; metres movedGET/info
Server metadata: coverage bbox, profiles, engine version, data dates, and the encoded values usable in
details, custom_model and /spt columns.
{ "bbox": [68.262955, 6.756888, 97.329353, 35.568624],
"profiles": [{ "name": "car" }],
"version": "12.0",
"elevation": false,
"import_date": "2026-09-20T11:59:21Z",
"data_date": "2026-04-24T20:20:51Z",
"encoded_values": { "road_class": ["OTHER", "MOTORWAY", /* … */], "max_speed": [">number", "<number"], /* … */ } }Errors
| Status | Body | Cause |
|---|---|---|
404 | nginx HTML page | Missing / wrong key, or an endpoint that isn't served (see below). |
400 | { "message": "Point 0 is out of bounds: …", "hints": [{ …, "point_index": 0 }] } | A point outside India. |
400 | The requested profile 'bike' does not exist. Available profiles: [car] | Unsupported profile. |
400 | You have to pass at least one point | No point given. |
Errors are { "message": "…", "hints": [{ "message", "details", … }] }.
Not served on this server: /matrix, /route-pt (public transit),
/navigate, /mvt vector tiles and geocoding. For many-to-many distances, call
/route with calc_points=false per pair.
Webhook (Aarambh → you)
The gateway calls your {{WEBHOOK_URI}} with the ar-access-key /
ar-secret-key pair. For the order/* POSTs below, Aarambh sends the full payload
(shown in getOrder); rider/allocate and rider/payment are described
below. You respond 200 with your own body, which is
passed straight back to the original caller.
| Your endpoint | When / payload |
|---|---|
POST {{WEBHOOK_URI}}/order/create | A new order is handed to you — the order/create shape. |
POST {{WEBHOOK_URI}}/order/cancel | { order_id, cancellation_reason_id, cancellation_reason, initiate_rto, pre_cancel_state }. |
POST {{WEBHOOK_URI}}/order/update | { order_id, status, timestamp, agent, tracking, end, merchant_order_id, retail_item_details }. |
POST {{WEBHOOK_URI}}/rider/allocate | A rider was assigned to an order — payload below. |
POST {{WEBHOOK_URI}}/rider/payment | A rider's payout for an order that ended RTO-Disposed — payload below. |
POST {{WEBHOOK_URI}}/notification/push | Relayed verbatim from your notification/create call. |
GET {{WEBHOOK_URI}}/order/status/get | Aarambh reads an order's status (query order_id, optionally bap_id / transaction_id). |
GET {{WEBHOOK_URI}}/agent/position | Aarambh reads a rider's position (query userCode). |
GET {{WEBHOOK_URI}}/order/getOrder/:id | Aarambh reads a full order from you by id. |
POST{{WEBHOOK_URI}}/rider/allocate
Aarambh pushes this when a rider is assigned to an order — the order moves from
Searching-for-Agent to Agent-assigned. It tells you which of your riders
(userCode) now holds the order, with the pickup and drop points.
- If your endpoint returns a
5xx, the push is retried up to 3 more times with backoff (0.5s, 1s, 2s), then dropped. A4xxis not retried. A failed push never affects the assignment. - You can receive the same payload more than once: key on
order_idand treat a repeat as the same allocation. - Respond
200; your body is not interpreted.
{
"order_id": "ORD-1788156784513-12|bdtest",
"transaction_id": "3f1d9a3c-5b8e-4f62-9d0a-6b7f2c1e8a44",
"task_id": "T-1788156784513",
"userCode": "sck0005",
"pickup": {
"location": { "gps": "12.9352,77.6245",
"address": { "name": "Store 12", "building": "", "locality": "Koramangala", "city": "Bengaluru", "state": "Karnataka", "country": "IND", "area_code": "560034" } },
"contact": { "phone": "9876543210", "email": "" },
"person": { "name": "Store 12" },
"time": { "range": { "start": "2026-10-06T10:00:00.000Z", "end": "2026-10-06T10:30:00.000Z" } },
"instructions": { "code": "2", "short_desc": "" }
},
"drop": { /* same shape as pickup */ }
}| Field | Type | Meaning |
|---|---|---|
order_id | string | The order. Always present. |
transaction_id | string | The order's transaction id on the buyer platform; empty string if absent. |
task_id | string | Aarambh's task id for the order; empty string if absent. |
userCode | string | The rider now assigned — the userCode returned by agent/create. |
pickup / drop | object | Same shape as in rider/payment: location.gps, location.address, contact, and when available person.name, time and instructions. |
POST{{WEBHOOK_URI}}/rider/payment
Aarambh pushes the rider's payout for an order as soon as it is settled. Today this fires when an order
ends RTO-Disposed — the rider could not hand over the parcel and the order is not
returned to the pickup point — so the rider (userCode) is paid for the attempt. The earning pushed here is the
same one later returned by payment/order_earning.
- If your endpoint returns a
5xx, the push is retried up to 3 more times with backoff (0.5s, 1s, 2s), then dropped. A4xxis not retried. A failed push never affects the order — usepayment/order_earningto reconcile anything you missed. - Because of retries you can receive the same payload more than once: key on
order_idand treat a repeat as the same payout, not a second one. - Respond
200; your body is not interpreted.
{
"order_id": "ORD-1788156784513-12|bdtest",
"status": "RTO-Disposed",
"amount": "64",
"orderId": "ORD-1788156784513-12|bdtest",
"fm_distance": 1.42,
"last_mile_distance": 5.8,
"breakup": {
"baseCharge": 54,
"rainCharge": 10,
"surgeLevel": 1,
"surgeLocation": "Koramangala",
"basePrice": 30,
"lmCharges": 18,
"fmCharges": 6,
"surgePrice": 0,
"rtoEarning": 0,
"pricingMode": "rate_card",
"pricePatternId": "6f1c2a9e-...",
"pricingSource": { "level": "agent_card", "patternId": "6f1c2a9e-...", "patternVersion": 3 }
},
"created_by": "<your subscriberid>",
"pricingMode": "rate_card",
"pickup": {
"location": { "gps": "12.9352,77.6245",
"address": { "name": "Store 12", "building": "", "locality": "Koramangala", "city": "Bengaluru", "state": "Karnataka", "country": "IND", "area_code": "560034" } },
"contact": { "phone": "9876543210", "email": "" },
"person": { "name": "Store 12" },
"time": { "range": { "start": "2026-10-06T10:00:00.000Z", "end": "2026-10-06T10:30:00.000Z" } },
"instructions": { "code": "2", "short_desc": "" }
},
"drop": { /* same shape as pickup */ },
"userCode": "sck0005"
}| Field | Type | Meaning |
|---|---|---|
order_id | string | The order. Always present. |
status | string | The status the order settled in. Currently always RTO-Disposed. |
amount | string | The rider's total payout, as a numeric string: breakup.baseCharge + breakup.rainCharge. |
orderId | string | Same as order_id. |
fm_distance | number | First-mile distance in km — from where the rider was when assigned to the pickup point. 0 when unknown. |
last_mile_distance | number | Pickup → drop distance in km. |
breakup.baseCharge | number | Payout excluding rain surge: basePrice + lmCharges + fmCharges + surgePrice + rtoEarning. |
breakup.rainCharge | number | Rain surge paid to the rider. |
breakup.surgeLevel | integer | Weather surge level: 0 none, 1 light, 2 moderate, 3 severe. |
breakup.surgeLocation | string | null | Area the weather surge applied to. |
breakup.basePrice | number | Base fare. |
breakup.lmCharges | number | Distance charge for the pickup → drop leg, beyond what the base fare covers. |
breakup.fmCharges | number | Charge for the rider's travel to the pickup point. |
breakup.surgePrice | number | Peak / time-slot surge. |
breakup.rtoEarning | number | Earning for a return leg; 0 when there was none. |
breakup.pricingMode | string | rate_card (rider paid off their own rate card) or relational (rider paid a share of the customer price). |
breakup.relationalBreakup | object | Only when pricingMode is relational: pricePatternId, slotTag, rtoSource, and components.base / .surge / .rainSurge, each { customer, agent, lspCut } — the customer price, the rider's share and the difference. |
breakup.pricePatternId | string | The pricing pattern used. |
breakup.pricingSource | object | { level, patternId, patternVersion } — which pricing configuration produced the payout. level is one of provider_agent, city_bap_agent, relational_quote_trail, agent_card, none. |
created_by | string | Your subscriberid. |
pricingMode | string | Same as breakup.pricingMode; defaults to rate_card. |
userCode | string | The rider who earned the payout — the userCode returned by agent/create. Empty string if the rider can't be resolved. |
pickup / drop | object | The order's pickup and drop points: location.gps ("lat,long"), location.address (name, building, locality, city, state, country, area_code), contact (phone, email), and when available person.name, time (duration, range.start / range.end, timestamp) and instructions (code, short_desc, additional_desc, images). |
All breakup amounts are whole rupees (truncated). If no pricing applied to the rider, every amount is 0 and pricingSource.level is none.
Status & verification rules
Strictly-incremental status progression
The forward track advances one step at a time:
| step | status |
|---|---|
| 1 | Agent-assigned |
| 2 | At-pickup |
| 3 | Order-picked-up |
| 4 | Out-for-delivery |
| 5 | At-delivery |
| 6 | Order-delivered (final) |
- Sending an earlier status →
409 STATUS_REGRESSION. - Sending the same status →
200no-op. - Sending a status more than one step ahead →
409 NON_INCREMENTAL_STATUS(the response tells you the one status you may send next). - The first status must be
Agent-assigned. RTO-Initiated,RTO-Delivered,Cancelledare not part of the forward track.
Pickup / delivery confirmation (PCC / DCC)
| Gate | Applies when | Confirm via order/event |
|---|---|---|
Pickup (before Order-picked-up) | the order has a pickup code | type: "scan" for a FIFO order, else type: "otp"; value = the pickup code |
Delivery (before Order-delivered) | the order has a delivery code | type: "otp"; value = the delivery code |
If the order has no code for a leg, that gate simply doesn't apply. Order-picked-up and
Order-delivered also require proof (proof.image or
proof.code) in the status call.
Return verification (RTO)
When an order carries an RTO code, RTO-Delivered is only accepted after an
order/event with type: "rto-verification" whose value matches that code.
Idempotency & failures
- Repeating
status/updatefor the same(order_id, status), orstatus/cancelfor the sameorder_id, within a short window returns200 { "idempotent": true }and is not re-processed. A failed attempt clears the window so a genuine retry goes through. - If a call the gateway makes to your endpoints fails, you get
502withpartnerStatus/partnerResponse— it is never reported as200.
End-to-end flows
1 · Rider onboarding
POST /v1/agent/createwithcurrentLocation,mobile,name(+ optionalnotification/device).- Response carries the
userCode— store it against your rider. - Use
POST /v1/agent/updatefor later changes andPOST /v1/agent/live-locationfor GPS pings.
2 · Happy-path delivery
status/update Agent-assigned(assignee = userCode).status/update At-pickup.order/event { type: "otp"|"scan", value: <pickup code> }— if the order has a pickup code.status/update Order-picked-upwithproof.status/update Out-for-delivery→status/update At-delivery.order/event { type: "otp", value: <delivery code> }— if the order has a delivery code.status/update Order-deliveredwithproof.
3 · Return (RTO)
- Order is past pickup.
status/update RTO-Initiated(orstatus/cancelwithis_rto: true). - Rider returns the parcel. If the order has an RTO code:
order/event { type: "rto-verification", value: <RTO code> }. status/update RTO-Delivered(only accepted while the order isRTO-Initiated).
4 · Cancellation
status/cancel { order_id, transaction_id, cancellation_reason_id }.- If the order was still pre-pickup it is cancelled; if it was past pickup it is turned into a return. Either way the buyer platform is notified.
5 · Callback request
order/event { type: "callbackrequest" }for the order.- Response
200 { "requestSupport": true }.
Error reference
| Status | error / code | Meaning |
|---|---|---|
400 | subscriberid header is required | Missing subscriberid. |
400 | field-specific (mobile 10 digits, coords 6 decimals, isActive must be a boolean, status must be one of …, proof … required, assignee is required) | Payload validation. |
400 | this order does not require RTO verification / no <KIND> confirmation is required … | The gate doesn't apply to this order / leg. |
400 | from query param is required in YYYY-MM-DD format / from is not a valid date / from must not be after to / date range cannot exceed 31 days | Payment date-range validation (same messages for to). |
401 | x-access-key and x-secret-key headers are required / Invalid x-access-key or x-secret-key | Bad credentials. |
404 | INVALID_USERCODE: no agent found for userCode "…" | assignee is not one of your riders. |
404 | no transaction found for order_id "…" / transaction_id "…" / no order found … | Order not found for your subscriber. |
404 | no earning found for order_id "…" | No earning settled for that order under your subscriber. |
404 | No agent found for subscriber "…" with UserCode "…" | userCode is not one of your riders. |
409 | STATUS_REGRESSION: cannot move task from "X" back to "Y" | Backwards status. |
409 | NON_INCREMENTAL_STATUS: task is "X"; the next allowed status is "Z", not "Y" | Skipped a step. |
409 | PCC_NOT_VERIFIED / PCC_SCAN_REQUIRED / DCC_NOT_VERIFIED | Confirm the pickup / delivery code via order/event first. |
409 | RTO_NOT_VERIFIED / RTO_MISMATCH / <KIND>_MISMATCH | Verification failed or missing. |
409 | RTO-Delivered requires the task to be RTO-Initiated (it is "…") | Out-of-order RTO completion. |
429 | Rate limit exceeded for this subscriber | Too many live-location pings (see Retry-After). |
502 | upstream failure (+ partnerStatus, partnerResponse) | A call the gateway made to your endpoints failed. |
200 | { "skipped": true, "reason": … } | Order already finished, or same status again. |
200 | { "idempotent": true } | Repeat within the idempotency window. |