Aarambh / LSP DMS integration

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

directions
            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 the order/create shape, 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/allocate and rider/payment to your {{WEBHOOK_URI}}, and reads order/status/get and agent/position from it. It also calls order/getNearest and order/request_callback on 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

DirectionSchemeHeaders
You → gateway (every route except health / docs)Subscriber key pair, issued to you per subscribersubscriberid, 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 Aarambhar-access-key, ar-secret-key
Gateway → your {{BASE_URI}} (order/getNearest, order/request_callback)Static bearer you provision for AarambhAuthorization
Server-side only

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 adds partnerStatus and partnerResponse.
  • Coordinates (agent endpoints): ["<lat>", "<long>"] — strings, each with exactly 6 digits after the decimal point (-?\d+\.\d{6}).
  • userCode: the agent code returned by agent/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.

request
{
  "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)

FieldRule
currentLocationrequired; array of exactly 2 strings, each -?\d+\.\d{6}
mobilerequired; string of exactly 10 digits
namerequired; non-empty string
notificationoptional. 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.
deviceoptional. device.manufacturer, if present, restricted to the provider enum.
isActiveoptional boolean
firstName, lastNameoptional strings. Default: name split at the first space.
profilePic, genderoptional strings. Default: "".
addressDetailsoptional. 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.
verifiedoptional; must be a real boolean. Default: true.
enabledoptional; one of 0 (disabled), 1 (enabled), 2 (blocked). Default: 1.

Notes

  • A second create for the same mobile is rejected (400).
  • userCode is generated by Aarambh — do not send your own.
  • notification is stored against the rider's user record; device is stored as a device record linked to the rider.
response
{ "message": "Agent created successfully",
  "data": { "userId": "...", "agentId": "...", "userCode": "sck0005", "vendorCode": "<subscriberid>" } }

POST/v1/agent/update

request
{
  "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" }
}
FieldRule
UserCoderequired; non-empty string
isActiveoptional; must be a real boolean — "true"/"false" strings and numbers are rejected 400
notification, devicesame rules as agent/create
isActive vs an active task

isActive: 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.

request
{ "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.

request
{
  "UserCode": "sck0005",
  "notifyToken": "<FCM device token for push notifications>"
}
FieldRule
UserCoderequired; non-empty string — the userCode returned by agent/create
notifyTokenoptional string. When sent, replaces the rider's stored push token; when omitted, the stored one is kept.
One active session per rider

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.

response
{ "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

request
{
  "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" }
}
FieldRule
order_id, transaction_idrequired, non-empty. Both must identify the same order for your subscriber.
statusrequired. Verbatim, case-sensitive, one of Agent-assigned, At-pickup, Order-picked-up, Out-for-delivery, At-delivery, Order-delivered, RTO-Initiated, RTO-Delivered, Cancelled.
assigneerequired; the rider's userCode. Must be a rider you registered — else 404 INVALID_USERCODE. (Rider name / mobile are not needed.)
call_back_supportoptional; "yes" / "no"
proofoptional. 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)

  1. The assignee resolves to one of your riders — else 404 INVALID_USERCODE.
  2. The order exists for your subscriber — else 404.
  3. The order isn't already finished (Order-delivered / RTO-Delivered / RTO-Disposed / Cancelled) — if it is, you get 200 { "skipped": true } and nothing changes.
  4. 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 → 200 no-op.
  5. 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-Delivered similarly needs an rto-verification event when the order carries an RTO code → 409 RTO_NOT_VERIFIED.
  6. Idempotent: repeating the same (order_id, status) within a short window returns 200 { "idempotent": true } and is not re-processed. A failed attempt clears the window so you can retry.

Behavior by status

statusEffect
Agent-assigned … Order-deliveredadvances the order one forward step (strictly incremental).
RTO-Initiatedstarts 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-Deliveredcompletes a return. Requires the order to currently be RTO-Initiated; the buyer platform is notified.
Cancelledcancels 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

request
{
  "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. true routes 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

request
{ "order_id": "ORD-...", "transaction_id": "TXN-...",
  "events": { "type": "otp", "value": "8309" } }
events.typeBehavior
otp / scanPickup / 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-verificationReturn 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 elseRecorded 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.

response · data
{
  "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"
}
FieldHow it's set
pickupProof / dropProofenabled: 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_enabledenabled when the order is configured for return-to-origin.
parcel_typeweight-banded — ≤1kg P1, ≤5kg P2, ≤15kg P3, else P4.
slapickup / 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.

request
{
  "order_id": "ORD-1788156784513-12|bdtest",
  "transaction_id": "TXN-1788156784513-12|bdtest",
  // ...any other fields your {{WEBHOOK_URI}}/notification/push endpoint expects
}
No payload schema is enforced

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 a notificationCreate event against that order's sync history (created if it doesn't already exist for your subscriber).
  • transaction_id — optional; stored alongside the sync record when order_id is present.
response
{ "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: from and to are YYYY-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 userCode returned from agent/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.

response
{ "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"
  } }
FieldMeaning
user_codeThe rider the earning was settled to — the one who completed the order.
amountThe rider's total earning for the order.
breakup.base_priceBase fare.
breakup.last_mile_chargesDistance charge for the pickup → drop leg, beyond what the base fare covers.
breakup.first_mile_chargesCharge for the rider's travel to the pickup point.
breakup.surgePeak / time-slot surge.
breakup.rto_earningEarning for the return leg, when the order went RTO.
breakup.rain_chargeRain surge.
breakup.adjustmentamount 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_distanceDistances in km for each leg. These are distances, not money.
typesystem_calculated, or manual_update when an operator corrected the amount.
earned_atWhen 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.

request
GET /v1/payment/rider_earning/sck0005?from=2026-09-01&to=2026-09-07
response
{ "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.

request
GET /v1/payment/rider_incentive/sck0005?from=2026-09-01&to=2026-09-07
response
{ "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, ... }
    ]
  } }
FieldMeaning
total_incentiveIncentive credited to the rider within the range — the sum of incentives[].amount.
breakuptotal_incentive split by incentive type: daily, weekly, festival.
type / periodThe incentive's cycle. period is YYYY-MM-DD for DAILY / FESTIVAL and YYYY-Www (ISO week) for WEEKLY.
incentive_onWhat the slabs count: ordercount (orders delivered) or earning (order earnings).
total_orders / total_earningsThe rider's progress in that cycle.
slabs[].slab_count / slab_amountThe slab's target and its configured payout.
slabs[].amountWhat 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_atWhen the slab was unlocked (UTC). Only slabs unlocked inside the range are listed.
Ranges add up

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 URIhttps://api-maps.aarambh.cloud
AuthYour maps key, as the key query param or the X-API-Key header. Issued separately from the gateway key pair.
CoverageIndia — bbox [68.262955, 6.756888, 97.329353, 35.568624] (min lon, min lat, max lon, max lat). Points outside → 400.
Profilescar only. Any other profile → 400.
CORSAccess-Control-Allow-Origin: *, methods GET, POST, HEAD, OPTIONS.
  • Coordinate order differs by format. The point query param is lat,lon (point=12.9716,77.5946). Every JSON / GeoJSON body and response — POST points, coordinates, bbox — is [lon, lat].
  • Units: distance in metres, time in milliseconds.
  • Polylines: points_encoded defaults to true (Google polyline, multiplier 1e5). Pass points_encoded=false for a GeoJSON LineString.
A bad key looks like a missing route

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.

Keep the key out of public bundles

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.

request
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}}
ParamDefaultMeaning
point—Required, repeat 2+ times: lat,lon. Start, any via points, destination — visited in the order given.
profile—car.
points_encodedtruefalse → geometry as GeoJSON LineString.
calc_pointstruefalse → distance / time only, no geometry. Fastest option for ETA-only lookups.
instructionstrueTurn-by-turn list. false to drop it.
localeenInstruction 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.
typejsongpx → 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.disablefalsetrue 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).
response
{ "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"] /* … */] }
  } ] }
FieldMeaning
paths[]One route; several with algorithm=alternative_route, best first.
snapped_waypointsWhere each input point landed on the road network, in input order.
instructions[].signManoeuvre: -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_timestampDate 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].

request
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_model rules: speed (limit_to km/h or multiply_by), priority (multiply_by; 0 forbids the road), and areas (GeoJSON polygons to reference as in_<area_id>). Conditions can use the encoded values listed by /info, e.g. road_class, road_environment, max_speed, roundabout, country.
  • A custom_model needs "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.

request
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>
response (excerpt)
{ "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, default 40): how far a ping may sit from the road. Raise it for noisy traces.
  • original_distance is the straight-line sum between pings; distance is 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.

request
GET https://api-maps.aarambh.cloud/isochrone?point=12.9716,77.5946&profile=car&time_limit=600&buckets=2&key={{MAPS_API_KEY}}
ParamDefaultMeaning
point—Required, lat,lon.
time_limit600Seconds.
distance_limit—Metres. Set instead of time_limit.
buckets1Split the limit into N equal rings, one polygon each (properties.bucket = 0…N-1, innermost first).
reverse_flowfalsetrue → the area from which the point can be reached (inbound), e.g. riders who can get to a pickup in time.
response
{ "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.

request
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}}
response · text/csv
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
…
  • columns defaults to longitude,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).
  • time is milliseconds, distance metres.

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.

request
GET https://api-maps.aarambh.cloud/nearest?point=12.9716,77.5946&profile=car&key={{MAPS_API_KEY}}
response
{ "type": "Point", "coordinates": [77.594567, 12.971606], "distance": 3.57 }   // snapped [lon, lat]; metres moved

GET/info

Server metadata: coverage bbox, profiles, engine version, data dates, and the encoded values usable in details, custom_model and /spt columns.

response (excerpt)
{ "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

StatusBodyCause
404nginx HTML pageMissing / 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.
400The requested profile 'bike' does not exist. Available profiles: [car]Unsupported profile.
400You have to pass at least one pointNo 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 endpointWhen / payload
POST {{WEBHOOK_URI}}/order/createA 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/allocateA rider was assigned to an order — payload below.
POST {{WEBHOOK_URI}}/rider/paymentA rider's payout for an order that ended RTO-Disposed — payload below.
POST {{WEBHOOK_URI}}/notification/pushRelayed verbatim from your notification/create call.
GET {{WEBHOOK_URI}}/order/status/getAarambh reads an order's status (query order_id, optionally bap_id / transaction_id).
GET {{WEBHOOK_URI}}/agent/positionAarambh reads a rider's position (query userCode).
GET {{WEBHOOK_URI}}/order/getOrder/:idAarambh 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. A 4xx is not retried. A failed push never affects the assignment.
  • You can receive the same payload more than once: key on order_id and treat a repeat as the same allocation.
  • Respond 200; your body is not interpreted.
request body
{
  "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 */ }
}
FieldTypeMeaning
order_idstringThe order. Always present.
transaction_idstringThe order's transaction id on the buyer platform; empty string if absent.
task_idstringAarambh's task id for the order; empty string if absent.
userCodestringThe rider now assigned — the userCode returned by agent/create.
pickup / dropobjectSame 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. A 4xx is not retried. A failed push never affects the order — use payment/order_earning to reconcile anything you missed.
  • Because of retries you can receive the same payload more than once: key on order_id and treat a repeat as the same payout, not a second one.
  • Respond 200; your body is not interpreted.
request body
{
  "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"
}
FieldTypeMeaning
order_idstringThe order. Always present.
statusstringThe status the order settled in. Currently always RTO-Disposed.
amountstringThe rider's total payout, as a numeric string: breakup.baseCharge + breakup.rainCharge.
orderIdstringSame as order_id.
fm_distancenumberFirst-mile distance in km — from where the rider was when assigned to the pickup point. 0 when unknown.
last_mile_distancenumberPickup → drop distance in km.
breakup.baseChargenumberPayout excluding rain surge: basePrice + lmCharges + fmCharges + surgePrice + rtoEarning.
breakup.rainChargenumberRain surge paid to the rider.
breakup.surgeLevelintegerWeather surge level: 0 none, 1 light, 2 moderate, 3 severe.
breakup.surgeLocationstring | nullArea the weather surge applied to.
breakup.basePricenumberBase fare.
breakup.lmChargesnumberDistance charge for the pickup → drop leg, beyond what the base fare covers.
breakup.fmChargesnumberCharge for the rider's travel to the pickup point.
breakup.surgePricenumberPeak / time-slot surge.
breakup.rtoEarningnumberEarning for a return leg; 0 when there was none.
breakup.pricingModestringrate_card (rider paid off their own rate card) or relational (rider paid a share of the customer price).
breakup.relationalBreakupobjectOnly 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.pricePatternIdstringThe pricing pattern used.
breakup.pricingSourceobject{ 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_bystringYour subscriberid.
pricingModestringSame as breakup.pricingMode; defaults to rate_card.
userCodestringThe rider who earned the payout — the userCode returned by agent/create. Empty string if the rider can't be resolved.
pickup / dropobjectThe 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).
Amounts

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:

stepstatus
1Agent-assigned
2At-pickup
3Order-picked-up
4Out-for-delivery
5At-delivery
6Order-delivered (final)
  • Sending an earlier status → 409 STATUS_REGRESSION.
  • Sending the same status → 200 no-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, Cancelled are not part of the forward track.

Pickup / delivery confirmation (PCC / DCC)

GateApplies whenConfirm via order/event
Pickup (before Order-picked-up)the order has a pickup codetype: "scan" for a FIFO order, else type: "otp"; value = the pickup code
Delivery (before Order-delivered)the order has a delivery codetype: "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/update for the same (order_id, status), or status/cancel for the same order_id, within a short window returns 200 { "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 502 with partnerStatus / partnerResponse — it is never reported as 200.

End-to-end flows

1 · Rider onboarding

  1. POST /v1/agent/create with currentLocation, mobile, name (+ optional notification / device).
  2. Response carries the userCode — store it against your rider.
  3. Use POST /v1/agent/update for later changes and POST /v1/agent/live-location for GPS pings.

2 · Happy-path delivery

  1. status/update Agent-assigned (assignee = userCode).
  2. status/update At-pickup.
  3. order/event { type: "otp"|"scan", value: <pickup code> } — if the order has a pickup code.
  4. status/update Order-picked-up with proof.
  5. status/update Out-for-delivery → status/update At-delivery.
  6. order/event { type: "otp", value: <delivery code> } — if the order has a delivery code.
  7. status/update Order-delivered with proof.

3 · Return (RTO)

  1. Order is past pickup. status/update RTO-Initiated (or status/cancel with is_rto: true).
  2. Rider returns the parcel. If the order has an RTO code: order/event { type: "rto-verification", value: <RTO code> }.
  3. status/update RTO-Delivered (only accepted while the order is RTO-Initiated).

4 · Cancellation

  1. status/cancel { order_id, transaction_id, cancellation_reason_id }.
  2. 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

  1. order/event { type: "callbackrequest" } for the order.
  2. Response 200 { "requestSupport": true }.

Error reference

Statuserror / codeMeaning
400subscriberid header is requiredMissing subscriberid.
400field-specific (mobile 10 digits, coords 6 decimals, isActive must be a boolean, status must be one of …, proof … required, assignee is required)Payload validation.
400this order does not require RTO verification / no <KIND> confirmation is required …The gate doesn't apply to this order / leg.
400from 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 daysPayment date-range validation (same messages for to).
401x-access-key and x-secret-key headers are required / Invalid x-access-key or x-secret-keyBad credentials.
404INVALID_USERCODE: no agent found for userCode "…"assignee is not one of your riders.
404no transaction found for order_id "…" / transaction_id "…" / no order found …Order not found for your subscriber.
404no earning found for order_id "…"No earning settled for that order under your subscriber.
404No agent found for subscriber "…" with UserCode "…"userCode is not one of your riders.
409STATUS_REGRESSION: cannot move task from "X" back to "Y"Backwards status.
409NON_INCREMENTAL_STATUS: task is "X"; the next allowed status is "Z", not "Y"Skipped a step.
409PCC_NOT_VERIFIED / PCC_SCAN_REQUIRED / DCC_NOT_VERIFIEDConfirm the pickup / delivery code via order/event first.
409RTO_NOT_VERIFIED / RTO_MISMATCH / <KIND>_MISMATCHVerification failed or missing.
409RTO-Delivered requires the task to be RTO-Initiated (it is "…")Out-of-order RTO completion.
429Rate limit exceeded for this subscriberToo many live-location pings (see Retry-After).
502upstream 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.