Files
hold-slayer/docs/api-reference.md
Robert Helewka dff21f7d5c Serve dashboard at /, version the REST API under /api/v1
The SvelteKit build was always made for the root (no base path); it
now mounts at / — registered last so /api/v1, /ws, /health, and /mcp
match first — and the JSON root endpoint is gone (its info lives in
/health and gateway_status). REST routers move from /api/* to
/api/v1/*; /ws and /health stay put; /mcp/ unchanged. Dashboard API
client, tests, README, and docs updated; dashboard rebuilt (build/ is
gitignored).

Verified live: / serves the UI, /api/v1 answers 200/401, the old
/api paths 404, MCP still lists 15 tools.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 14:57:45 -04:00

7.1 KiB

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/v1/calls/outbound

Request:

{
  "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:

{
  "call_id": "call_abc123",
  "status": "trying",
  "number": "+18005551234",
  "mode": "hold_slayer",
  "started_at": "2026-01-15T10:30:00Z"
}

Launch Hold Slayer

POST /api/v1/calls/hold-slayer

Convenience endpoint — equivalent to POST /outbound with mode=hold_slayer.

Request:

{
  "number": "+18005551234",
  "intent": "dispute Amazon charge from December 15th",
  "call_flow_id": "chase_bank_disputes",
  "transfer_to": "sip_phone"
}

Get Call Status

GET /api/v1/calls/{call_id}

Response:

{
  "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/v1/calls

Response:

{
  "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/v1/calls/{call_id}/hangup

Transfer a Call

POST /api/v1/calls/{call_id}/transfer

Request:

{
  "device": "sip_phone"
}

Call Flows

List Call Flows

GET /api/v1/call-flows
GET /api/v1/call-flows?company=Chase+Bank
GET /api/v1/call-flows?tag=banking

Response:

{
  "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/v1/call-flows/{flow_id}

Returns the full call flow with all steps.

Create Call Flow

POST /api/v1/call-flows

Request:

{
  "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/v1/call-flows/{flow_id}

Delete Call Flow

DELETE /api/v1/call-flows/{flow_id}

Devices

List Registered Devices

GET /api/v1/devices

Response:

{
  "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/v1/devices

Request:

{
  "name": "Office SIP Phone",
  "type": "sip_phone",
  "sip_uri": "sip:robert@gateway.helu.ca",
  "priority": 10,
  "capabilities": ["voice"]
}

Update Device

PUT /api/v1/devices/{device_id}

Remove Device

DELETE /api/v1/devices/{device_id}

Error Responses

All errors follow a consistent format:

{
  "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:

{
  "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

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

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())