ResponseOneDispatch APIOpenAPI 3 spec

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.

REST + JSONBearer API keys per companyIdempotentHMAC-signed webhooksOpenAPI 3

Staging base URL: https://responseone-test-api.baishoroa.workers.dev/v1

Quick start

  1. 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.
  2. Find your units. GET /v1/units lists units that are on duty. Unit IDs are also listed on the admin page.
  3. Send a call. POST /v1/assignments with the unit and the address. The crew’s app shows it within about 10 seconds.
  4. Get status back. Add a webhook (POST /v1/webhooks or 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

FieldType
unitIdstringRequired. From GET /v1/units or the admin page.
addressstring ≤ 200Street address. Send this, or lat+lng, or both (best: exact pin plus the address the crew reads).
lat, lngnumberWGS84.
prioritystringemergency (default), urgent or routine. Sets the color of the call on the crew’s screen.
notesstring ≤ 500Shown to the crew: entrance, floor, complaint.
externalIdstring ≤ 64Your 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

StatusWhen
availableOn duty, no active call; or the transport is finished.
en_routeNavigating to the call.
on_sceneCrew tapped On scene, or arrived and stopped within about 150 m of the address.
transportingStarted a new route after being on scene (to the hospital).
off_dutyWent 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 to pending, delivered, accepted, completed, cancelled or superseded.
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 (429 with Retry-After).
  • 1,000 calls per company per 24 hours (ask us to raise it).
  • Errors look like {"error":{"code":"unit_not_found","message":"…"}}: 400 invalid request, 401 bad or revoked key, 403 company plan not active, 404 not in your company, 409 idempotency conflict, 429 slow 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.