Skip to main content
BedKoi

BedKoi API for ambulance companies' software

Connected to BedKoi, your company's own dispatch software can take requests, report each step of a trip and say which vehicles are free by itself. The panel's rules apply. This guide is for the software's developers.

This guide as a PDF:বাংলা (PDF)English (PDF)

Getting started

  1. The company's admin signs in to the BedKoi panel and makes an API key on the company's “Connect software” page. The key is shown once; keep it secret. A hospital's key for its beds doesn't work here.
  2. Send the key with every request, in the X-Api-Key header. Every address starts with: https://api.bedkoi.com/v1
  3. Keep saying which vehicles are free: requests come only for vehicles marked free.
curl https://api.bedkoi.com/v1/partner/ambulance/requests \
  -H "X-Api-Key: bk_live_…"

Endpoints

  • GET/partner/ambulance/providerYour company: name, status, the districts it serves
  • GET/partner/ambulance/requestsRequests offered to you now (offers), oldest first; and your trips under way (trips)
  • POST/partner/ambulance/requests/{tripId}/acceptTake it: etaMinutes (minutes to the pickup), and vehicleId and driverId if you know them
  • POST/partner/ambulance/requests/{tripId}/declinePass on it
  • GET/partner/ambulance/tripsYour trips (status=active or done, page, pageSize)
  • GET/partner/ambulance/trips/{tripId}One trip: patient, the family's phone, destination, steps, fare
  • POST/partner/ambulance/trips/{tripId}/assignSet or swap the vehicle and driver (vehicleId, driverId); a new driver is texted the trip's link
  • POST/partner/ambulance/trips/{tripId}/stepThe next step, in order: ON_WAY (left), ARRIVED (at the pickup), PICKED_UP (patient in), DROPPED (at the destination)
  • POST/partner/ambulance/trips/{tripId}/locationWhere the vehicle is (lat, lng, from your GPS), until the patient is dropped; the family sees it on their page
  • POST/partner/ambulance/trips/{tripId}/give-backYou can't go after all (until the patient is in): the request goes to the others again (reason)
  • POST/partner/ambulance/trips/{tripId}/fareThe final fare from your fare table: km, waitMinutes, oxygenHours, bearer, tollTaka; well above the estimate it needs a reason
  • POST/partner/ambulance/trips/{tripId}/paid-cashThe family paid the driver in cash: the trip is over
  • GET/partner/ambulance/vehiclesYour vehicles, each with its state and how long it counts as free (freeUntil)
  • PUT/partner/ambulance/vehicles/{vehicleId}/availableFree or off: { "available": true }
  • GET/partner/ambulance/driversYour drivers

Taking a request

A new request comes as news (ambulance.offer), or check requests. A few nearby companies get it at once; the first to take it gets it, and a late answer gets 409 already_taken. The family's phone number is shown only once you've taken it.

curl -X POST https://api.bedkoi.com/v1/partner/ambulance/requests/<tripId>/accept \
  -H "X-Api-Key: bk_live_…" \
  -H "Content-Type: application/json" \
  -d '{ "etaMinutes": 12, "vehicleId": "01a2…", "driverId": "01a3…" }'

Running a trip

Set the vehicle and driver, then send the steps in order; the family is texted when you leave and arrive. If the vehicle has GPS, keep sending where it is. The driver can also take steps from their link; your software still hears of it (viaPartner: false). Send the fare at the destination; paid-cash once it's paid in cash.

curl -X POST https://api.bedkoi.com/v1/partner/ambulance/trips/<tripId>/step \
  -H "X-Api-Key: bk_live_…" \
  -H "Content-Type: application/json" \
  -d '{ "step": "ON_WAY" }'

curl -X POST https://api.bedkoi.com/v1/partner/ambulance/trips/<tripId>/location \
  -H "X-Api-Key: bk_live_…" \
  -H "Content-Type: application/json" \
  -d '{ "lat": 23.7461, "lng": 90.3742 }'

curl -X POST https://api.bedkoi.com/v1/partner/ambulance/trips/<tripId>/fare \
  -H "X-Api-Key: bk_live_…" \
  -H "Content-Type: application/json" \
  -d '{ "km": 6.4, "waitMinutes": 15 }'

Free vehicles

Requests come only for vehicles marked free, and “free” wears off after a while (freeUntil). While a vehicle is free, say so again now and then; a vehicle on a trip is left out by itself.

curl -X PUT https://api.bedkoi.com/v1/partner/ambulance/vehicles/<vehicleId>/available \
  -H "X-Api-Key: bk_live_…" \
  -H "Content-Type: application/json" \
  -d '{ "available": true }'

Request news (webhooks)

Give an https address on the “Connect software” page in the panel. BedKoi POSTs each message there, signed, and tries again a few times if it gets no answer. Every message has tripId and reference; viaPartner says whether your software did it.

Events

  • ambulance.offerA request is offered to you (details in offer); first to take it gets it
  • ambulance.offer_closedAn offer you didn't answer is gone: reason is taken (another company took it), cancelled or expired
  • ambulance.trip_updatedA trip of yours changed (the whole trip in trip). change: accepted, assigned, step, fare, fare_answer (the family accepted the fare or said it isn't right), paid, fare_decided (BedKoi settled a complaint)
  • ambulance.trip_cancelledThe family or BedKoi called off a trip of yours; no need to send the vehicle
  • ambulance.trip_given_backThe trip was given back (from your panel or software); it's no longer yours
  • ping“Send a test” in the panel
POST https://dispatch.example.com/bedkoi
X-BedKoi-Event: ambulance.offer
X-BedKoi-Delivery: 01a1…
X-BedKoi-Timestamp: 1761552000
X-BedKoi-Signature: v1=5d41…

{
  "event": "ambulance.offer",
  "occurredAt": "2026-10-27T08:00:00.000Z",
  "tripId": "01a1…",
  "reference": "AM7K3Q9",
  "viaPartner": false,
  "offer": {
    "tripId": "01a1…",
    "reference": "AM7K3Q9",
    "type": { "id": 2, "key": "ac", "nameEn": "AC ambulance", "nameBn": "এসি অ্যাম্বুলেন্স", "critical": false, "forDeceased": false },
    "scheduledFor": null,
    "pickup": { "address": "Road 12, Dhanmondi", "lat": 23.746, "lng": 90.376, "area": "ধানমন্ডি" },
    "drop": { "address": "…", "lat": 23.738, "lng": 90.396, "area": "শাহবাগ", "hospital": { "nameEn": "…", "nameBn": "…" } },
    "estimateKm": 4.2,
    "estimateMinTaka": 1800,
    "estimateMaxTaka": 2100,
    "condition": "শ্বাসকষ্ট",
    "floor": 3,
    "hasLift": false,
    "needsBearer": true,
    "distanceKm": 1.8,
    "round": 0,
    "sentAt": "2026-10-27T08:00:00.000Z",
    "createdAt": "2026-10-27T08:00:00.000Z"
  }
}
{
  "event": "ambulance.trip_updated",
  "occurredAt": "2026-10-27T08:14:00.000Z",
  "tripId": "01a1…",
  "reference": "AM7K3Q9",
  "viaPartner": false,
  "change": "step",
  "trip": { "id": "01a1…", "status": "ARRIVED", "phone": "+8801…", "patientName": "…", "vehicleId": "01a2…", "driverId": "01a3…", "fare": { … } }
}

Check the signature

X-BedKoi-Signature is v1= followed by HMAC-SHA256(secret, timestamp + "." + body), with the timestamp in X-BedKoi-Timestamp. Drop a message whose signature doesn't match or that is more than 5 minutes old. The same message can arrive twice: X-BedKoi-Delivery tells them apart.

// Node.js
import { createHmac, timingSafeEqual } from "node:crypto"

function fromBedKoi(rawBody, headers, secret) {
  const ts = headers["x-bedkoi-timestamp"]
  const sent = headers["x-bedkoi-signature"] ?? ""
  if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false
  const expected =
    "v1=" + createHmac("sha256", secret).update(ts + "." + rawBody).digest("hex")
  return (
    sent.length === expected.length &&
    timingSafeEqual(Buffer.from(sent), Buffer.from(expected))
  )
}
<?php
$raw = file_get_contents('php://input');
$ts  = $_SERVER['HTTP_X_BEDKOI_TIMESTAMP'] ?? '';
$sig = $_SERVER['HTTP_X_BEDKOI_SIGNATURE'] ?? '';
$expected = 'v1=' . hash_hmac('sha256', $ts . '.' . $raw, getenv('BEDKOI_WEBHOOK_SECRET'));
if (abs(time() - (int) $ts) > 300 || !hash_equals($expected, $sig)) {
    http_response_code(401);
    exit;
}
$event = json_decode($raw, true);
http_response_code(200);

Errors

  • 400What you sent isn't right; the message says why.
  • 401No key, or a wrong or revoked one (api_key_missing, api_key_invalid).
  • 404No such request, trip or vehicle at your company.
  • 409The trip already changed: already_taken (another company took it), offer_closed, not_after_… (a step out of order), vehicle_busy (on another trip), provider_stopped (your company can't take requests now).
  • 429More than 120 requests a minute; try again shortly.

Need help?

Write to [email protected]