Integrate EzyRunners delivery into your application using a simple REST API.
The EzyRunners Enterprise API lets you create deliveries, track orders in real-time, and receive webhook notifications — all authenticated with a single API key you generate from your dashboard.
Every request must include your API key in the X-API-Key header. You can generate or rotate your key on the Settings → Integration page.
X-API-Key: YOUR_API_KEY Content-Type: application/json
X-API-Key, not Authorization: Bearer.
Client applications send requests to the EzyRunners server. All endpoints are prefixed with /api/v1/integrations.
| Environment | EzyRunners Base URL |
|---|---|
| Production | https://ezyrunners.com/api/v1/integrations |
Example full endpoint: https://ezyrunners.com/api/v1/integrations/delivery/create
Limits are enforced per API key per minute. When exceeded, the API returns 429 Too Many Requests.
| Plan | Requests / Minute |
|---|---|
| Starter | 60 |
| Business | 300 |
| Enterprise | 10,000 |
Contact us to upgrade your plan if you need higher limits.
EzyRunners uses a prepaid wallet system. You add funds to your enterprise wallet through the dashboard, and every delivery automatically deducts the delivery fee from your balance at the moment the order is created.
POST /delivery/create with "payment_method": "prepaid", the system calculates the delivery fee and immediately deducts it from your wallet.wallet_deducted: true, delivery_fee (exact amount charged), and wallet_balance (remaining balance after deduction).pending or assigned (runner not yet picked up), the full delivery fee is automatically refunded to your wallet. The cancel response includes refund_amount.picked_up / in_transit / delivered), cancellation is not allowed.402 Payment Required with the required, available, and shortfall amounts.
HTTP 402 Payment Required
{
"success": false,
"error": "insufficient_balance",
"message": "Insufficient wallet balance",
"required": 85.00,
"available": 30.00,
"shortfall": 55.00
}
payment_method is cod, the delivery fee is still deducted from your wallet upfront. The customer pays the cod_amount in cash to the runner, who submits it separately.
To prevent duplicate deliveries on network retries, pass a unique X-Idempotency-Key header or include an order_id field. If the same key is seen within 24 hours, the original response is replayed without creating a new delivery or deducting the wallet again.
X-API-Key: YOUR_API_KEY X-Idempotency-Key: ORDER-20260330-0042 Content-Type: application/json
| HTTP | error field | Meaning |
|---|---|---|
400 | missing_required_field | A required field is absent |
400 | delivery_not_cancellable | Delivery status doesn't allow cancellation |
401 | missing_api_key | X-API-Key header was not sent |
401 | invalid_api_key | Key not found or inactive |
401 | expired_api_key | Key has passed its expiry date |
402 | insufficient_balance | Wallet balance too low |
403 | account_suspended | Enterprise account suspended |
403 | forbidden | Delivery belongs to a different enterprise |
404 | not_found | Delivery ID not found |
409 | duplicate_order_id | order_id was already used |
422 | invalid_coordinates | Latitude/longitude out of range |
429 | rate_limit_exceeded | Too many requests this minute |
500 | internal_error | Server-side error |
Creates a new delivery. The delivery fee is immediately deducted from your wallet (prepaid) or collected from the customer (cod). Supports idempotency via X-Idempotency-Key header or order_id field.
{
"order_id": "YOUR-ORDER-123", // optional, for idempotency & dedup
"pickup_location": {
"address": "123 Main Street, Hyderabad",
"latitude": 17.4239,
"longitude": 78.4738,
"contact_name": "Store Manager",
"contact_phone": "+919876543210",
"instructions": "Ask for order #123"
},
"drop_location": {
"address": "456 Customer Lane, Hyderabad",
"latitude": 17.4456,
"longitude": 78.4892,
"contact_name": "John Doe",
"contact_phone": "+919876543211",
"instructions": "Leave at door"
},
"package_details": {
"type": "general", // general | medicine | food | documents
"weight_kg": 2.5,
"is_fragile": false,
"requires_cold_storage": false,
"description": "Medicine package"
},
"vehicle_type": "two_wheeler", // two_wheeler (default) | bike | bike_plus | auto | mini_truck | truck
"payment_method": "prepaid", // prepaid (wallet) | cod
"order_value": 450.00, // value of goods (for COD)
"cod_amount": 0, // cash to collect from customer (if cod)
"priority": "normal", // normal | express (1.5x) | urgent (2x)
"notes": "Handle with care"
}
201 Created
{
"success": true,
"delivery_id": "EZY20260330AB1234",
"internal_id": "65a1b2c3d4e5f6789abc1234", // MongoDB internal ID
"status": "pending",
"tracking_url": "/track/EZY20260330AB1234",
"delivery_fee": 85.00,
"wallet_deducted": 85.00, // amount deducted from wallet (same as delivery_fee)
"wallet_balance": 9845.00, // remaining wallet balance after deduction (INR)
"estimated_pickup_time": "5-15 minutes",
"message": "Delivery created. Finding runner..."
}
delivery_fee is deducted from your wallet the instant this endpoint succeeds. If your balance is insufficient, the API returns 402 and no delivery is created.
Retrieve full details and live status of a delivery. delivery_id can be either the EzyRunners delivery ID (EZY…) or your own order_id.
{
"success": true,
"delivery_id": "EZY20260330AB1234",
"order_id": "YOUR-ORDER-123",
"status": "in_transit", // pending | assigned | picked_up | in_transit | delivered | cancelled | failed
"pickup": { "address": "...", "latitude": 17.4239, "longitude": 78.4738 },
"drop": { "address": "...", "latitude": 17.4456, "longitude": 78.4892 },
"delivery_fee": 85.00,
"distance_km": 4.3,
"tracking_url": "/track/EZY20260330AB1234",
"runner_details": { // present when a runner is assigned
"name": "Rahul K",
"phone": "+91987654****"
},
"current_location": { // present when runner is en route
"latitude": 17.4350,
"longitude": 78.4820,
"updated_at": "2026-03-30T14:45:00Z"
},
"estimated_arrival": "8 minutes",
"distance_remaining_km": 1.5,
"timestamps": {
"created_at": "2026-03-30T14:00:00Z",
"assigned_at": "2026-03-30T14:05:00Z",
"picked_at": "2026-03-30T14:25:00Z",
"delivered_at": null
},
"proof_of_delivery": { // present only when status = delivered
"photos": ["..."],
"signature": null,
"notes": "Left with security"
}
}
Cancel a delivery. Only deliveries in pending or assigned status can be cancelled. Once a runner has picked up the package, cancellation is not allowed.
{
"reason": "customer_request", // customer_request | store_unavailable | item_unavailable | payment_failed | other
"notes": "Customer changed their mind",
"refund_delivery_fee": true // default true — refunds delivery fee to wallet
}
{
"success": true,
"delivery_id": "EZY20260330AB1234",
"status": "cancelled",
"refund_amount": 85.00, // amount refunded to wallet (0 if refund_delivery_fee=false)
"message": "Delivery cancelled successfully"
}
refund_delivery_fee is true (default), the full delivery fee is instantly credited back to your wallet.
Get shareable tracking links and an embeddable map URL for a delivery.
{
"success": true,
"tracking_url": "https://ezyrunners.com/track/EZY20260330AB1234",
"embed_url": "https://ezyrunners.com/track/EZY20260330AB1234?embed=true",
"qr_code_url": "https://ezyrunners.com/api/v1/integrations/delivery/EZY20260330AB1234/qr"
}
Create up to 50 deliveries in a single API call. Each delivery in the array uses the same format as the single create endpoint. Wallet fee is deducted for each successful delivery individually.
{
"deliveries": [
{
"order_id": "ORD-001",
"pickup_location": { "..." },
"drop_location": { "..." },
"package_details": { "..." },
"payment_method": "prepaid"
},
{ "..." }
]
}
{
"success": true,
"total": 3,
"created": 2,
"failed": 1,
"results": [
{ "index": 0, "success": true, "delivery_id": "EZY20260330AB1234", "status": "pending" },
{ "index": 1, "success": true, "delivery_id": "EZY20260330CD5678", "status": "pending" },
{ "index": 2, "success": false, "error": "insufficient_balance" }
]
}
Check how many online runners are available near a pickup point before placing an order.
| Parameter | Type | Required | Description |
|---|---|---|---|
latitude | float | Yes | Pickup latitude |
longitude | float | Yes | Pickup longitude |
radius_km | float | No | Search radius in km (default: 5) |
{
"success": true,
"available_runners": [
{
"runner_id": "65a1b2c3...",
"name": "Rahul K",
"distance_km": 1.2,
"vehicle_type": "bike",
"rating": 4.8,
"eta_minutes": 4
}
],
"total_available": 7,
"search_radius_km": 5
}
Calculate the delivery fee before placing an order. No wallet deduction — this is a dry run only.
{
"pickup_latitude": 17.4239,
"pickup_longitude": 78.4738,
"drop_latitude": 17.4456,
"drop_longitude": 78.4892,
"weight_kg": 2.5,
"vehicle_type": "two_wheeler", // two_wheeler (default) | bike | bike_plus | auto | mini_truck | truck
"package_type": "general",
"priority": "normal" // normal | express | urgent
}
{
"success": true,
"estimate": {
"distance_km": 4.30,
"estimated_time_minutes": 10,
"vehicle_type": "bike",
"pricing": {
"base_fare": 19.00,
"distance_fare": 33.00,
"weight_surcharge": 0,
"vehicle_charge": 0,
"platform_fee": 9.36, // 18% of subtotal
"total_fare": 61.36,
"currency": "INR"
},
"priority_multiplier": 1.0,
"estimated_pickup_time": "5-10 minutes"
}
}
Fetch the live pricing configuration to display fees to your customers or build your own fee calculator.
delivery_fee = (base_fare
+ max(0, distance_km - 1) × per_km_rate
+ weight_surcharge
+ vehicle_charge) × priority_multiplier
+ platform_fee (18%)
| Surcharge | Value |
|---|---|
| Night delivery (22:00–06:00) | 1.25× multiplier |
| Fragile handling | +₹15 flat |
| Cold storage required | +₹25 flat |
| Express priority | 1.5× multiplier |
| Urgent priority | 2.0× multiplier |
Register a URL on your server where EzyRunners will POST real-time delivery events. You can register one webhook URL per enterprise account.
{
"url": "https://yourapp.com/webhooks/ezyrunners",
"events": ["delivery.assigned", "delivery.picked_up", "delivery.delivered"],
"secret": "your_custom_secret" // optional — auto-generated if omitted
}
201 Created
{
"success": true,
"data": {
"webhook_id": "65a1b2c3d4e5f6789",
"url": "https://yourapp.com/webhooks/ezyrunners",
"events": ["delivery.assigned", "delivery.picked_up", "delivery.delivered"],
"secret": "whsec_abc123xyz..." // save this to verify incoming webhooks
}
}
EzyRunners will POST a JSON payload to your URL for each subscribed event.
| Event | Fired when |
|---|---|
delivery.created | A new delivery is created via API |
delivery.assigned | A runner accepts the delivery |
delivery.picked_up | Runner picks up the package |
delivery.in_transit | Runner is heading to the drop point |
delivery.delivered | Package delivered successfully |
delivery.cancelled | Delivery cancelled (with refund amount) |
delivery.failed | Delivery could not be completed |
runner.location_update | Runner GPS location changed |
runner.eta_update | Estimated arrival time changed |
{
"event": "delivery.delivered",
"event_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"delivery_id": "EZY20260330AB1234",
"status": "delivered",
"timestamp": 1743340920, // Unix epoch (integer, not ISO string)
"data": {
"delivery_id": "EZY20260330AB1234",
"status": "delivered",
"pickup_address": "123 Main Street, Hyderabad",
"drop_address": "456 Customer Lane, Hyderabad",
"runner_name": "Rahul K",
"runner_phone": "+91987654****",
"delivery_fee": 85.00,
"distance_km": 4.3
}
}
Every webhook POST includes an X-EzyRunners-Signature header. Use it to verify the request genuinely came from EzyRunners.
EzyRunners signs requests using the HMAC-SHA256 of "{timestamp}.{raw_payload}". The timestamp comes from the X-EzyRunners-Timestamp header (Unix integer as a string).
# Python verification example import hmac, hashlib def verify_webhook(request, secret): received_sig = request.headers.get('X-EzyRunners-Signature') # "sha256=..." timestamp = request.headers.get('X-EzyRunners-Timestamp') # Unix int as string raw_body = request.get_data() # raw bytes unchanged # Message is: "{timestamp}.{raw_body}" message = f"{timestamp}.{raw_body.decode('utf-8')}" expected = "sha256=" + hmac.new( secret.encode('utf-8'), message.encode('utf-8'), hashlib.sha256 ).hexdigest() return hmac.compare_digest(expected, received_sig)
hmac.compare_digest (constant-time comparison) to prevent timing attacks. Never compare signatures with ==.
Retrieve the history of webhook deliveries, including failed attempts with error details.
| Parameter | Type | Description |
|---|---|---|
page | int | Page number (default: 1) |
limit | int | Results per page, max 100 (default: 20) |
event | string | Filter by event type |
status | string | success or failed |
Send a signed test event to the webhook URL currently saved in Settings → Integration. No request body is needed. Use this to confirm your server is reachable and your HMAC verification logic is correct before going live.
POST /api/v1/integrations/webhook/test
X-API-Key: YOUR_API_KEY
// No request body required.
// EzyRunners sends a signed test payload to your configured webhook URL.
{
"success": true,
"message": "Test webhook delivered successfully",
"status_code": 200,
"response_time_ms": 145
}
Generate your API key from the Settings → Integration page and start creating deliveries in minutes.