# API Reference Hold Slayer exposes a REST API, WebSocket endpoint, and MCP server. ## REST API Base URL: `http://localhost:8000/api` ### Calls #### Place an Outbound Call ``` POST /api/calls/outbound ``` **Request:** ```json { "number": "+18005551234", "mode": "hold_slayer", "intent": "dispute Amazon charge from December 15th", "device": "sip_phone", "call_flow_id": "chase_bank_disputes", "services": { "recording": true, "transcription": true } } ``` **Call Modes:** | Mode | Description | |------|-------------| | `direct` | Dial and connect to your device immediately | | `hold_slayer` | Navigate IVR, wait on hold, transfer when human detected | | `ai_assisted` | Connect with noise cancel, transcription, recording | **Response:** ```json { "call_id": "call_abc123", "status": "trying", "number": "+18005551234", "mode": "hold_slayer", "started_at": "2026-01-15T10:30:00Z" } ``` #### Launch Hold Slayer ``` POST /api/calls/hold-slayer ``` Convenience endpoint — equivalent to `POST /outbound` with `mode=hold_slayer`. **Request:** ```json { "number": "+18005551234", "intent": "dispute Amazon charge from December 15th", "call_flow_id": "chase_bank_disputes", "transfer_to": "sip_phone" } ``` #### Get Call Status ``` GET /api/calls/{call_id} ``` **Response:** ```json { "call_id": "call_abc123", "status": "on_hold", "number": "+18005551234", "mode": "hold_slayer", "duration": 847, "hold_time": 780, "audio_type": "music", "transcript_excerpt": "...your call is important to us...", "classification_history": [ {"timestamp": 1706000000, "type": "ringing", "confidence": 0.95}, {"timestamp": 1706000003, "type": "ivr_prompt", "confidence": 0.88}, {"timestamp": 1706000010, "type": "music", "confidence": 0.92} ], "services": {"recording": true, "transcription": true} } ``` #### List Active Calls ``` GET /api/calls ``` **Response:** ```json { "calls": [ {"call_id": "call_abc123", "status": "on_hold", "number": "+18005551234", "duration": 847}, {"call_id": "call_def456", "status": "connected", "number": "+18009876543", "duration": 120} ], "total": 2 } ``` #### End a Call ``` POST /api/calls/{call_id}/hangup ``` #### Transfer a Call ``` POST /api/calls/{call_id}/transfer ``` **Request:** ```json { "device": "sip_phone" } ``` ### Call Flows #### List Call Flows ``` GET /api/call-flows GET /api/call-flows?company=Chase+Bank GET /api/call-flows?tag=banking ``` **Response:** ```json { "flows": [ { "id": "chase_bank_disputes", "name": "Chase Bank — Disputes", "company": "Chase Bank", "phone_number": "+18005551234", "step_count": 7, "success_count": 12, "fail_count": 1, "tags": ["banking", "disputes"] } ] } ``` #### Get Call Flow ``` GET /api/call-flows/{flow_id} ``` Returns the full call flow with all steps. #### Create Call Flow ``` POST /api/call-flows ``` **Request:** ```json { "name": "Chase Bank — Disputes", "company": "Chase Bank", "phone_number": "+18005551234", "steps": [ {"id": "wait", "type": "WAIT", "description": "Wait for greeting", "timeout": 5.0, "next_step": "menu"}, {"id": "menu", "type": "LISTEN", "description": "Main menu", "next_step": "press3"}, {"id": "press3", "type": "DTMF", "description": "Account services", "dtmf": "3", "next_step": "hold"}, {"id": "hold", "type": "HOLD", "description": "Wait for agent", "next_step": "transfer"}, {"id": "transfer", "type": "TRANSFER", "description": "Connect to user"} ] } ``` #### Update Call Flow ``` PUT /api/call-flows/{flow_id} ``` #### Delete Call Flow ``` DELETE /api/call-flows/{flow_id} ``` ### Devices #### List Registered Devices ``` GET /api/devices ``` **Response:** ```json { "devices": [ { "id": "dev_001", "name": "Office SIP Phone", "type": "sip_phone", "sip_uri": "sip:robert@gateway.helu.ca", "is_online": true, "priority": 10 } ] } ``` #### Register a Device ``` POST /api/devices ``` **Request:** ```json { "name": "Office SIP Phone", "type": "sip_phone", "sip_uri": "sip:robert@gateway.helu.ca", "priority": 10, "capabilities": ["voice"] } ``` #### Update Device ``` PUT /api/devices/{device_id} ``` #### Remove Device ``` DELETE /api/devices/{device_id} ``` ### Error Responses All errors follow a consistent format: ```json { "detail": "Call not found: call_xyz789" } ``` | Status Code | Meaning | |-------------|---------| | `400` | Bad request (invalid parameters) | | `404` | Resource not found (call, flow, device) | | `409` | Conflict (call already ended, device already registered) | | `500` | Internal server error | ## WebSocket ### Event Stream ``` ws://localhost:8000/ws/events ws://localhost:8000/ws/events?call_id=call_abc123 ws://localhost:8000/ws/events?types=human_detected,hold_detected ``` **Query Parameters:** | Param | Description | |-------|-------------| | `call_id` | Filter events for a specific call | | `types` | Comma-separated event types to receive | **Event Format:** ```json { "type": "hold_detected", "call_id": "call_abc123", "timestamp": "2026-01-15T10:35:00Z", "data": { "audio_type": "music", "confidence": 0.92, "hold_duration": 0 } } ``` ### Event Types | Type | Data Fields | |------|------------| | `call_started` | `number`, `mode`, `intent` | | `call_ringing` | `number` | | `call_connected` | `number`, `duration` | | `call_ended` | `number`, `duration`, `reason` | | `call_failed` | `number`, `error` | | `hold_detected` | `audio_type`, `confidence` | | `human_detected` | `confidence`, `transcript_excerpt` | | `transfer_started` | `device`, `from_call_id` | | `transfer_complete` | `device`, `bridge_id` | | `ivr_step` | `step_id`, `step_type`, `description` | | `ivr_dtmf_sent` | `digits`, `step_id` | | `ivr_menu_detected` | `transcript`, `options` | | `audio_classified` | `audio_type`, `confidence`, `features` | | `transcript_chunk` | `text`, `speaker`, `is_final` | | `recording_started` | `recording_id`, `path` | | `recording_stopped` | `recording_id`, `duration`, `file_size` | ### Client Example ```javascript const ws = new WebSocket("ws://localhost:8000/ws/events"); ws.onopen = () => { console.log("Connected to Hold Slayer events"); }; ws.onmessage = (event) => { const data = JSON.parse(event.data); switch (data.type) { case "human_detected": alert("🚨 A live person picked up! Pick up your phone!"); break; case "hold_detected": console.log("⏳ On hold..."); break; case "transcript_chunk": console.log(`📝 ${data.data.speaker}: ${data.data.text}`); break; } }; ws.onerror = (error) => { console.error("WebSocket error:", error); }; ``` ### Python Client Example ```python import asyncio import websockets import json async def listen(): async with websockets.connect("ws://localhost:8000/ws/events") as ws: async for message in ws: event = json.loads(message) print(f"[{event['type']}] {event.get('data', {})}") asyncio.run(listen()) ```