API docs
How to test ExceptionOps from a terminal: send signed machine events, see how the engine classifies and dispatches them, then work the task as a field operator and watch the proof get verified.
All data here is mock-up data. The fleet, robots, vehicles, operators, coordinates and keys are synthetic, and nothing is connected to a real machine. The demo resets every 30 minutes, so exception and task ids start again from EX-1 / TASK-1.
Quick start: one exception, start to finish
Base URL https://exceptionops.enthernetservice.com. Send a demo token as Authorization: Bearer <token>.
# 1. The fleet reports an open vehicle door
curl -s -X POST -H "Authorization: Bearer demo-dispatcher" https://exceptionops.enthernetservice.com/api/v1/demo/emit/A
# → {"exception_id":"EX-1","status":"DISPATCHED",…}
# 2. See who it was offered to (matching.ranked[0]) and the task id (current_task)
curl -s -H "Authorization: Bearer demo-viewer" https://exceptionops.enthernetservice.com/api/v1/exceptions/EX-1
# 3. The operator accepts, starts and resolves it with proof
curl -s -X POST -H "Authorization: Bearer demo-op-OP-12" https://exceptionops.enthernetservice.com/api/v1/tasks/TASK-1/accept
curl -s -X POST -H "Authorization: Bearer demo-op-OP-12" https://exceptionops.enthernetservice.com/api/v1/tasks/TASK-1/start
curl -s -X POST -H "Authorization: Bearer demo-op-OP-12" -H "Content-Type: application/json" \
-d '{"resolution_type":"FIXED_ON_SITE","evidence":[{"type":"PHOTO","content":{"caption":"door closed"}},{"type":"CHECKLIST","content":{"door_closed":true,"cabin_clear":true}}],"telemetry":{"door_state":"CLOSED"},"checklist":{"door_closed":true,"cabin_clear":true}}' \
https://exceptionops.enthernetservice.com/api/v1/tasks/TASK-1/resolve
# → status CLOSED: proof present, hashes match, telemetry confirms; resume signal issued
# 4. Read the hash-chained audit trail
curl -s -H "Authorization: Bearer demo-auditor" https://exceptionops.enthernetservice.com/api/v1/exceptions/EX-1/audit
If others have run scenarios since the last reset, use the exception id step 1 returns, and the operator and task id from step 2 (matching.ranked[0].operator_id, current_task).
Try it
Response appears here.
Credentials
Static demo tokens for synthetic users (production uses SSO). The integration key and secret sign machine events.
| Bearer token | Who |
|---|---|
demo-owner | Owner · everything |
demo-dispatcher | Dispatcher · emit, classify, cancel |
demo-auditor | Auditor · read + audit trail |
demo-viewer | Viewer · read only |
demo-op-OP-11 | Field operator OP-11 |
demo-op-OP-12 | Field operator OP-12 |
demo-op-OP-14 | Field operator OP-14 |
demo-op-OP-16 | Field operator OP-16 |
demo-other-owner | Owner of another tenant · sees none of the demo fleet |
key-demo-fleet / demo-secret-rotate-me | Integration key and HMAC secret of the synthetic fleet |
Endpoints
Intake (machines)
POST/api/v1/exceptionsSigned event from a fleet or robot
Who: integration key (key-demo-fleet)
BODY='{"event_id":"EV-2001","asset_id":"BOT-19","event_type":"PATH_BLOCKED","severity_hint":"MEDIUM",
"metadata":{"safety_state":"SAFE"},"location":[0.015,0.012],"telemetry":{"path_state":"BLOCKED"},
"timestamp":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}'
TS=$(date +%s)
SIG=$(printf '%s' "$TS.$BODY" | openssl dgst -sha256 -hmac demo-secret-rotate-me | sed 's/^.* //')
curl -X POST -H "Content-Type: application/json" \
-H "X-ExceptionOps-Key: key-demo-fleet" -H "X-ExceptionOps-Timestamp: $TS" -H "X-ExceptionOps-Signature: $SIG" \
-d "$BODY" https://exceptionops.enthernetservice.com/api/v1/exceptions{"exception_id": "EX-2", "status": "DISPATCHED", "duplicate": false}Signature: hex(HMAC-SHA256(secret, "<timestamp>." + raw_body)). Rejections come back as 401 with MISSING_HEADERS, UNKNOWN_KEY, BAD_TIMESTAMP, STALE_TIMESTAMP, BAD_SIGNATURE or REPLAY (a signature can only be used once). A repeated event_id returns 200 with "duplicate": true. Body max 64 KB; intake is rate-limited per key.
Fields: event_id, asset_id (AV-104, BOT-19, BOT-42, BOT-77…), event_type, severity_hint, metadata, location [lat, lon], telemetry, timestamp.
POST/api/v1/demo/emit/{A|B|C|D}Have the synthetic fleet sign and send a scenario event
Who: owner, dispatcher
curl -X POST -H "Authorization: Bearer demo-dispatcher" https://exceptionops.enthernetservice.com/api/v1/demo/emit/A
{"exception_id": "EX-1", "status": "DISPATCHED", "duplicate": false}A = open vehicle door (rule match, dispatch) · B = robot obstruction · C = ambiguous telemetry (no rule; AI suggests, a human confirms) · D = critical mechanical fault.
Dispatchers
GET/api/v1/exceptionsAll exceptions for your tenant
Who: any token
curl -H "Authorization: Bearer demo-viewer" https://exceptionops.enthernetservice.com/api/v1/exceptions
GET/api/v1/exceptions/{id}History, policy decision and operator matching for one exception
Who: any token
curl -H "Authorization: Bearer demo-viewer" https://exceptionops.enthernetservice.com/api/v1/exceptions/EX-1
{
"id": "EX-1",
"asset_external_id": "AV-104",
"event_type": "OPEN_DOOR",
"type": "OPEN_DOOR",
"severity": "LOW",
"status": "DISPATCHED",
"classification_method": "DETERMINISTIC",
"ai_used": false,
"current_task": "TASK-1",
"history": [
{
"state": "VERIFIED",
"actor_type": "INTEGRATION",
"explanation": "HMAC signature, timestamp window and replay check passed."
},
{
"state": "DISPATCHED",
"actor_type": "SYSTEM",
"explanation": "Offered to OP-12 (score 85.6, 0.8 km, ETA 2 min). Respond within 60s."
}
],
"matching": {
"ranked": [
{
"operator_id": "OP-12",
"score": 85.6,
"distance_km": 0.8
}
],
"excluded": [
{
"operator_id": "OP-14",
"reasons": [
"missing skill BASIC_FIELD_ASSISTANCE"
]
}
]
},
"decision": {
"policy_id": "POL-AV-OPEN-DOOR",
"required_skill": "BASIC_FIELD_ASSISTANCE",
"sla_seconds": 480,
"proof": [
"PHOTO",
"CHECKLIST"
],
"expected_telemetry": {
"door_state": "CLOSED"
},
"checklist": [
"door_closed",
"cabin_clear"
]
}
}GET/api/v1/exceptions/{id}/auditHash-chained audit trail
Who: owner, auditor
curl -H "Authorization: Bearer demo-auditor" https://exceptionops.enthernetservice.com/api/v1/exceptions/EX-1/audit
[
{
"seq": 1,
"actor_type": "INTEGRATION",
"actor_id": "key-demo-fleet",
"event_type": "EXCEPTION_RECEIVED",
"resource_id": "EX-1",
"prev_hash": "0000\u2026",
"hash": "41ef8c13\u2026"
}
]POST/api/v1/exceptions/{id}/ai-reviewAsk the advisory model for a classification suggestion
Who: owner, dispatcher
curl -X POST -H "Authorization: Bearer demo-dispatcher" https://exceptionops.enthernetservice.com/api/v1/exceptions/EX-3/ai-review
Stored as ai_suggestion (model, suggested_classification, confidence, explanation). It never changes the type or dispatches anyone.
POST/api/v1/exceptions/{id}/confirm-classificationA human sets the type of an unclassified exception
Who: owner, dispatcher
curl -X POST -H "Authorization: Bearer demo-dispatcher" -H "Content-Type: application/json" \
-d '{"exception_type":"OBSTRUCTION","note":"Matches drive current and the AI suggestion"}' \
https://exceptionops.enthernetservice.com/api/v1/exceptions/EX-3/confirm-classificationThe exception becomes HUMAN_CONFIRMED and goes on to policy and matching.
POST/api/v1/exceptions/{id}/cancelCancel an exception
Who: owner, dispatcher
curl -X POST -H "Authorization: Bearer demo-dispatcher" -H "Content-Type: application/json" \
-d '{"reason":"Duplicate of EX-1"}' https://exceptionops.enthernetservice.com/api/v1/exceptions/EX-2/cancelField operators
POST/api/v1/tasks/{task_id}/acceptAccept an offered task
Who: the operator it was offered to
curl -X POST -H "Authorization: Bearer demo-op-OP-12" https://exceptionops.enthernetservice.com/api/v1/tasks/TASK-1/accept
Use the operator from matching.ranked[0] and the task from current_task. Anyone else gets 403.
POST/api/v1/tasks/{task_id}/rejectTurn down an offer; the next eligible operator is offered it
Who: the operator it was offered to
curl -X POST -H "Authorization: Bearer demo-op-OP-12" https://exceptionops.enthernetservice.com/api/v1/tasks/TASK-1/reject
POST/api/v1/tasks/{task_id}/startMark the task in progress
Who: the assigned operator
curl -X POST -H "Authorization: Bearer demo-op-OP-12" https://exceptionops.enthernetservice.com/api/v1/tasks/TASK-1/start
POST/api/v1/tasks/{task_id}/resolveSubmit the fix with proof; the engine verifies it
Who: the assigned operator
curl -X POST -H "Authorization: Bearer demo-op-OP-12" -H "Content-Type: application/json" \
-d '{"resolution_type":"FIXED_ON_SITE","notes":"Door closed, cabin clear",
"evidence":[{"type":"PHOTO","content":{"caption":"rear left door closed"}},
{"type":"CHECKLIST","content":{"door_closed":true,"cabin_clear":true}}],
"telemetry":{"door_state":"CLOSED"},
"checklist":{"door_closed":true,"cabin_clear":true}}' \
https://exceptionops.enthernetservice.com/api/v1/tasks/TASK-1/resolveHistory then shows RESOLVED → VERIFICATION_PENDING → VERIFIED_COMPLETE ("Proof present, hashes match, telemetry confirms") → CLOSED ("Resume signal issued"). Missing proof or telemetry that disagrees fails verification. resolution_type: FIXED_ON_SITE or UNABLE_TO_RESOLVE; evidence types: PHOTO, CHECKLIST, CONFIRMATION_CODE, TELEMETRY.
Status
GET/api/v1/dashboardLive counters for your tenant
Who: owner, dispatcher, auditor, viewer
curl -H "Authorization: Bearer demo-viewer" https://exceptionops.enthernetservice.com/api/v1/dashboard
{
"demo_mode": true,
"active_assets": 5,
"open_exceptions": 0,
"pending_dispatches": 0,
"operators_available": 4,
"avg_dispatch_seconds": null,
"avg_resolution_seconds": null,
"sla_breaches": 0,
"ai_assisted_cases": 0,
"escalated": 0
}GET/healthzHealth and mode
Who: anyone
curl https://exceptionops.enthernetservice.com/healthz
{"ok": true, "demo_mode": true, "ai_enabled": true}States
RECEIVED → VERIFIED → CLASSIFIED → MATCHING → DISPATCHED → ACCEPTED → IN_PROGRESS → RESOLVED → VERIFICATION_PENDING → VERIFIED_COMPLETE → CLOSED
Side paths: UNCLASSIFIED (no rule matched; waits for a human, AI may only suggest), NO_OPERATOR_AVAILABLE, DISPATCH_TIMEOUT and OPERATOR_REJECTED (offered to the next operator), RESOLUTION_FAILED, VERIFICATION_FAILED, ESCALATED and CANCELLED. Every step is in history with who did it and why.
Errors
401 | {"detail":"invalid token"} for users; intake errors like {"error":"REPLAY"} |
403 | {"error":"FORBIDDEN","detail":"role VIEWER lacks audit.read"}, or another tenant's exception |
404 | {"error":"NOT_FOUND"} |
409 | {"error":"ILLEGAL_TRANSITION"}: the action doesn't fit the current state |
413 | Intake body over 64 KB |
422 | INVALID_EVENT or a body that fails validation |
429 | Rate limited |
