API Keys Documentation
Authentication, endpoints, request formats, and response examples for the v1/calls API
Overview
API keys give external systems programmatic access to trigger, retrieve, and cancel voice calls. Each key is scoped to specific permissions — only grant what your integration actually needs.
Only Indian phone numbers in E.164 format (+91XXXXXXXXXX) are currently supported.
Base URL
https://api.huskyvoice.ai/v1
Authentication
Include your API key in the Authorization header of every request.
Authorization: Bearer sk_live_your_api_key_here
Content-Type: application/json
Security: Store your API key in an environment variable. Never hardcode it in source code or commit it to version control.
Permissions (Scopes)
Each API key is created with one or more scopes. Grant only the scopes your integration requires.
| Scope | Allows |
|---|---|
calls:create | Schedule new voice calls via POST /v1/calls |
calls:read | Retrieve call status and details via GET /v1/calls/:id |
calls:cancel | Cancel pending calls via POST /v1/calls/:id/cancel |
calls:delete | Permanently delete a call and erase customer PII via DELETE /v1/calls/:id |
appointments:read | Fetch appointments, configuration, services, and availability |
appointments:write | Create and update appointments |
slots:read | Read slot availability and schedule tracking records |
slots:write | Create, update, and disable calendar windows; update and regenerate tracking |
Endpoints
Schedule a call
POST /v1/calls
Requires calls:create scope
Schedule a voice call with an agent. Supports idempotent creation via the Idempotency-Key header.
Request Body
{
"agent_id": "agent_123", // required
"contact_number": "+919876543210", // required — E.164 format, +91 only
"contact_name": "Raj Patel", // optional
"contact_email": "raj@company.com",// optional
"additional_info": { // optional — custom key-value pairs
"company": "Acme Corp",
"deal_stage": "negotiation"
}
}
Response — 201 Created
{
"data": {
"call_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "scheduled"
}
}
Retrieve call status
GET /v1/calls/:call_id
Requires calls:read scope
Retrieve status and details for a specific call.
Response — 200 OK
{
"data": {
"call_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "scheduled", // scheduled | in_progress | completed | failed | cancelled
"agent_id": "agent_123",
"contact_number": "+919876543210",
"contact_name": "Raj Patel",
"contact_email": "raj@company.com",
"additional_info": { "company": "Acme Corp" },
"scheduled_at": "2024-01-27T03:02:00.000Z",
"completed_at": null,
"error": null,
"created_at": "2024-01-27T03:00:00.000Z",
"updated_at": "2024-01-27T03:00:00.000Z"
}
}
Cancel a call
POST /v1/calls/:call_id/cancel
Requires calls:cancel scope
Cancel a call in pending or scheduled status. Returns 409 if already started or completed.
Response — 200 OK
{
"data": {
"call_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "cancelled",
"cancelled_at": "2024-01-27T03:01:00.000Z"
}
}
Delete a call
DELETE /v1/calls/:call_id
Requires calls:delete scope
Permanently deletes a completed call and erases all associated end-customer PII (transcript, contact details, recording metadata). Only works on calls that have a recorded engagement. Returns 404 if the call is not found. See Deleting Calls for full details.
Response — 200 OK
{
"data": {
"call_id": "550e8400-e29b-41d4-a716-446655440000",
"deleted": true
}
}
Error Codes
| Code | Meaning |
|---|---|
400 | Bad request — missing or invalid fields in the request body |
401 | Unauthorized — missing or invalid API key |
403 | Forbidden — API key does not have the required scope |
409 | Conflict — call cannot be cancelled because it has already started or completed |
422 | Unprocessable entity — invalid phone number format or unsupported country code |
429 | Rate limit exceeded — slow down requests and retry after the indicated delay |
Examples
Full working scripts covering create, status check, and cancel.
- cURL
- Python
- JavaScript
- TypeScript
- n8n
#!/bin/bash
API_KEY="$APPEQ_API_KEY"
BASE_URL="https://api.huskyvoice.ai/v1"
# ── Create a call ──────────────────────────────────────────────────────────
CALL_ID=$(curl -s -X POST "$BASE_URL/calls" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"agent_id": "agent_123",
"contact_number": "+919876543210",
"contact_name": "Raj Patel",
"contact_email": "raj@company.com",
"additional_info": {"company": "Acme Corp", "deal_stage": "negotiation"}
}' | jq -r '.data.call_id')
echo "Created: $CALL_ID"
# ── Get call status ────────────────────────────────────────────────────────
STATUS=$(curl -s "$BASE_URL/calls/$CALL_ID" \
-H "Authorization: Bearer $API_KEY" | jq -r '.data.status')
echo "Status: $STATUS"
# ── Cancel a call ──────────────────────────────────────────────────────────
CANCEL_STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
-X POST "$BASE_URL/calls/$CALL_ID/cancel" \
-H "Authorization: Bearer $API_KEY")
if [ "$CANCEL_STATUS" = "409" ]; then
echo "Cannot cancel — call already started or completed"
else
echo "Cancelled successfully"
fi
import os
import requests
API_KEY = os.environ["APPEQ_API_KEY"]
BASE_URL = "https://api.huskyvoice.ai/v1"
session = requests.Session()
session.headers.update({
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
})
# ── Create a call ──────────────────────────────────────────────────────────
res = session.post(f"{BASE_URL}/calls", json={
"agent_id": "agent_123",
"contact_number": "+919876543210",
"contact_name": "Raj Patel",
"contact_email": "raj@company.com",
"additional_info": {"company": "Acme Corp", "deal_stage": "negotiation"},
})
res.raise_for_status()
call_id = res.json()["data"]["call_id"]
print(f"Created: {call_id}")
# ── Get call status ────────────────────────────────────────────────────────
res = session.get(f"{BASE_URL}/calls/{call_id}")
res.raise_for_status()
call = res.json()["data"]
print(f"Status: {call['status']}")
# ── Cancel a call ──────────────────────────────────────────────────────────
res = session.post(f"{BASE_URL}/calls/{call_id}/cancel")
if res.status_code == 409:
print("Cannot cancel — call already started or completed")
else:
res.raise_for_status()
print(f"Cancelled: {res.json()['data']['status']}")
const API_KEY = process.env.APPEQ_API_KEY;
const BASE_URL = "https://api.huskyvoice.ai/v1";
const headers = {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
};
// ── Create a call ──────────────────────────────────────────────────────────
const createRes = await fetch(`${BASE_URL}/calls`, {
method: "POST",
headers,
body: JSON.stringify({
agent_id: "agent_123",
contact_number: "+919876543210",
contact_name: "Raj Patel",
contact_email: "raj@company.com",
additional_info: { company: "Acme Corp", deal_stage: "negotiation" },
}),
});
if (!createRes.ok) throw new Error(`Create failed: ${createRes.status}`);
const { data: { call_id } } = await createRes.json();
console.log("Created:", call_id);
// ── Get call status ────────────────────────────────────────────────────────
const statusRes = await fetch(`${BASE_URL}/calls/${call_id}`, { headers });
if (!statusRes.ok) throw new Error(`Status failed: ${statusRes.status}`);
const { data: call } = await statusRes.json();
console.log("Status:", call.status);
// ── Cancel a call ──────────────────────────────────────────────────────────
const cancelRes = await fetch(`${BASE_URL}/calls/${call_id}/cancel`, {
method: "POST",
headers,
});
if (cancelRes.status === 409) {
console.log("Cannot cancel — call already started or completed");
} else if (!cancelRes.ok) {
throw new Error(`Cancel failed: ${cancelRes.status}`);
} else {
const { data } = await cancelRes.json();
console.log("Cancelled:", data.status);
}
const API_KEY = process.env.APPEQ_API_KEY!;
const BASE_URL = "https://api.huskyvoice.ai/v1";
interface CallData {
call_id: string;
status: string;
agent_id?: string;
contact_number?: string;
scheduled_at?: string;
completed_at?: string | null;
cancelled_at?: string;
}
interface ApiResponse<T> { data: T }
const headers: HeadersInit = {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
};
async function apiFetch<T>(path: string, init?: RequestInit): Promise<T> {
const res = await fetch(`${BASE_URL}${path}`, { ...init, headers });
if (!res.ok) {
throw new Error(`${res.status} ${res.statusText}: ${await res.text()}`);
}
return (await res.json() as ApiResponse<T>).data;
}
// ── Create a call ──────────────────────────────────────────────────────────
const created = await apiFetch<CallData>("/calls", {
method: "POST",
body: JSON.stringify({
agent_id: "agent_123",
contact_number: "+919876543210",
contact_name: "Raj Patel",
contact_email: "raj@company.com",
additional_info: { company: "Acme Corp", deal_stage: "negotiation" },
}),
});
console.log("Created:", created.call_id);
// ── Get call status ────────────────────────────────────────────────────────
const call = await apiFetch<CallData>(`/calls/${created.call_id}`);
console.log("Status:", call.status);
// ── Cancel a call ──────────────────────────────────────────────────────────
try {
const cancelled = await apiFetch<CallData>(`/calls/${created.call_id}/cancel`, {
method: "POST",
});
console.log("Cancelled:", cancelled.status);
} catch (err: unknown) {
if (err instanceof Error && err.message.startsWith("409")) {
console.log("Cannot cancel — call already started or completed");
} else {
throw err;
}
}
Create an HTTP Header Auth credential in n8n: set Name to Authorization and Value to Bearer YOUR_API_KEY. Select it in each HTTP Request node's Authentication field.
{
"name": "HuskyVoice – Call Lifecycle",
"nodes": [
{
"parameters": {
"method": "POST",
"url": "https://api.huskyvoice.ai/v1/calls",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "{\n \"agent_id\": \"agent_123\",\n \"contact_number\": \"+919876543210\",\n \"contact_name\": \"Raj Patel\",\n \"contact_email\": \"raj@company.com\",\n \"additional_info\": {\"company\": \"Acme Corp\", \"deal_stage\": \"negotiation\"}\n}"
},
"id": "1",
"name": "Create Call",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [250, 300]
},
{
"parameters": {
"method": "GET",
"url": "={{ 'https://api.huskyvoice.ai/v1/calls/' + $('Create Call').item.json.data.call_id }}",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth"
},
"id": "2",
"name": "Get Call Status",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [450, 300]
},
{
"parameters": {
"method": "POST",
"url": "={{ 'https://api.huskyvoice.ai/v1/calls/' + $('Create Call').item.json.data.call_id + '/cancel' }}",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth"
},
"id": "3",
"name": "Cancel Call",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [650, 300]
}
],
"connections": {
"Create Call": {
"main": [[{ "node": "Get Call Status", "type": "main", "index": 0 }]]
},
"Get Call Status": {
"main": [[{ "node": "Cancel Call", "type": "main", "index": 0 }]]
}
},
"settings": {},
"meta": { "instanceId": "huskyvoice-docs" }
}