Connect any dispatch system to ResponseOne
Your CAD sends a call to a unit, and it opens on that crew’s phone with the route ready: one tap and they’re driving, with voice turn-by-turn and the exact house. Your CAD sees where on-duty units are and gets a signed webhook every time a unit goes en route, on scene, transporting, or available.
Staging base URL: https://responseone-test-api.baishoroa.workers.dev/v1
Quick start
- Get a key. A company admin opens Admin › Dispatch API (CAD link) and taps Create API key. The key is shown once; we store only its hash.
- Find your units.
GET /v1/unitslists units that are on duty. Unit IDs are also listed on the admin page. - Send a call.
POST /v1/assignmentswith the unit and the address. The crew’s app shows it within about 10 seconds. - Get status back. Add a webhook (
POST /v1/webhooksor the admin page) and verify the signature.
curl -X POST https://responseone-test-api.baishoroa.workers.dev/v1/assignments \
-H "Authorization: Bearer $R1_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: CAD-2026-114233-medic12" \
-d '{"unitId":"UNIT_ID","externalId":"CAD-2026-114233",
"address":"1124 Avenue L, Brooklyn, NY","lat":40.6195,"lng":-73.9608,
"priority":"emergency","notes":"Side entrance, 2nd floor"}'
API keys
Every request sends Authorization: Bearer r1k_…. Keys belong to one company and only ever see that company’s units and calls. Company admins create and revoke keys on the admin page; a revoked key stops working at once. Keys are stored as SHA-256 hashes and can’t be shown again, so keep them in your CAD’s secret store.
curl https://responseone-test-api.baishoroa.workers.dev/v1/me -H "Authorization: Bearer $R1_KEY"
# {"company":{"id":"…","name":"Metro EMS"},"key":{"id":"…","name":"Main CAD"}}
Units
GET /v1/units returns on-duty units only. Off-duty crews share no location, by design. GET /v1/units/{unitId} returns one unit (status off_duty with no position when off duty).
curl https://responseone-test-api.baishoroa.workers.dev/v1/units -H "Authorization: Bearer $R1_KEY"
{"units":[{"id":"u_123","name":"Medic 12","status":"en_route",
"position":{"lat":40.6262,"lng":-73.9442,"heading":182,"speedMps":11.2,"updatedAt":"2026-11-04T03:10:00.000Z","stale":false},
"destination":{"label":"1124 Avenue L","lat":40.6195,"lng":-73.9608},
"etaAt":"2026-11-04T03:14:30.000Z","assignmentId":"asg_1a2b3c4d5e6f7a8b"}],
"onDutyOnly":true,"at":"2026-11-04T03:10:04.000Z"}
GET /v1/closures lists streets your crews blocked (shared to every unit of your company; each expires on its own).
Positions update about every 15 seconds on a trip and every 30 seconds otherwise. stale: true means no fix for 10 minutes (no signal).
Send a call to a unit
POST /v1/assignments
| Field | Type | |
|---|---|---|
unitId | string | Required. From GET /v1/units or the admin page. |
address | string ≤ 200 | Street address. Send this, or lat+lng, or both (best: exact pin plus the address the crew reads). |
lat, lng | number | WGS84. |
priority | string | emergency (default), urgent or routine. Sets the color of the call on the crew’s screen. |
notes | string ≤ 500 | Shown to the crew: entrance, floor, complaint. |
externalId | string ≤ 64 | Your CAD incident number. Shown to the crew and echoed in every webhook. |
Idempotency. Send an Idempotency-Key header (letters, digits, . _ : -, up to 100). If a request times out, retry with the same key: you get the original call back (200, header Idempotent-Replayed: true), never a second call. The same key with a different unit or address is a 409.
One open call per unit. A new call to a unit replaces its open one (the old one becomes superseded). A call to an off-duty unit waits and pops up when the crew goes on duty (the response carries a warning).
{"assignment":{"id":"asg_1a2b3c4d5e6f7a8b","unitId":"u_123","unitName":"Medic 12",
"externalId":"CAD-2026-114233","status":"pending","priority":"emergency",
"address":"1124 Avenue L, Brooklyn, NY","location":{"lat":40.6195,"lng":-73.9608},
"notes":"Side entrance, 2nd floor","createdAt":"2026-11-04T03:09:02.000Z", …}}
Also: GET /v1/assignments?status=open|all&limit=25, GET /v1/assignments/{id}, and DELETE /v1/assignments/{id} to cancel (the call disappears from the crew’s phone).
Call lifecycle
pending (waiting for the phone) → delivered (on the crew’s screen) → accepted (crew tapped Start route) → completed (unit back to available). Or cancelled / superseded.
Unit statuses
| Status | When |
|---|---|
available | On duty, no active call; or the transport is finished. |
en_route | Navigating to the call. |
on_scene | Crew tapped On scene, or arrived and stopped within about 150 m of the address. |
transporting | Started a new route after being on scene (to the hospital). |
off_duty | Went off duty. No position is sent after this. |
Statuses are set automatically from what the crew does in the app, and the crew can also set them by hand from the call card.
Webhooks
Add an https URL on the admin page or with the API. Up to 5 per company. Events:
unit.status_changed:{unitId, unitName, status, previousStatus, assignmentId, externalId, position, source}assignment.updated: the call moved topending,delivered,accepted,completed,cancelledorsuperseded.
curl -X POST https://responseone-test-api.baishoroa.workers.dev/v1/webhooks -H "Authorization: Bearer $R1_KEY" \
-H "Content-Type: application/json" \
-d '{"url":"https://cad.example.com/hooks/responseone","events":["unit.status_changed","assignment.updated"]}'
# returns {"webhook":{"id":"wh_…","secret":"whsec_…",…}} ← save the secret, shown once
curl -X POST https://responseone-test-api.baishoroa.workers.dev/v1/webhooks/WH_ID/test -H "Authorization: Bearer $R1_KEY"
POST /hooks/responseone
X-ResponseOne-Event: unit.status_changed
X-ResponseOne-Delivery: evt_9f2c41d0a3b8e771
X-ResponseOne-Signature: t=1793761800,v1=5f1c…e0
{"id":"evt_9f2c41d0a3b8e771","type":"unit.status_changed","createdAt":"2026-11-04T03:10:00.000Z",
"data":{"unitId":"u_123","unitName":"Medic 12","status":"on_scene","previousStatus":"en_route",
"assignmentId":"asg_1a2b3c4d5e6f7a8b","externalId":"CAD-2026-114233",
"position":{"lat":40.6195,"lng":-73.9608},"source":"auto"}}
Answer with any 2xx within 8 seconds. After a 5xx or no answer we retry once. Use the event id to ignore duplicates.
Verify the signature
v1 is the hex HMAC-SHA256 of "<t>.<raw body>" with your webhook secret. Compare in constant time and reject events whose t is more than 5 minutes old.
// Node.js
const crypto = require('crypto');
function verify(rawBody, header, secret) {
const { t, v1 } = Object.fromEntries(header.split(',').map(p => p.split('=')));
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
const want = crypto.createHmac('sha256', secret).update(t + '.' + rawBody).digest('hex');
return crypto.timingSafeEqual(Buffer.from(want), Buffer.from(v1));
}
# Python
import hmac, hashlib, time
def verify(raw_body: bytes, header: str, secret: str) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
if abs(time.time() - int(parts["t"])) > 300: return False
want = hmac.new(secret.encode(), parts["t"].encode() + b"." + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(want, parts["v1"])
Limits & errors
- 120 requests per minute per key (
429withRetry-After). - 1,000 calls per company per 24 hours (ask us to raise it).
- Errors look like
{"error":{"code":"unit_not_found","message":"…"}}:400invalid request,401bad or revoked key,403company plan not active,404not in your company,409idempotency conflict,429slow down. - Browser apps may call the API directly (CORS is open); keep keys server-side in production.
Test console
Runs in your browser against staging. Your key stays on this page (not saved).
Paste a key and press a button.
