API Documentation
Integrate Phone Stack into your workflow. Manage contacts, trigger calls, control campaigns, and receive real-time webhooks — all through a simple REST API.
Authentication
All API requests require a Bearer token in the Authorization header. Create API keys in your Phone Stack dashboard.
curl https://phonestack.com/api/v1/calls \
-H "Authorization: Bearer ps_live_YOUR_API_KEY"Key prefixes:
ps_live_— Production keys with full accessps_test_— Test keys (read-only, no real calls)
Permissions: Full, Read Only, Write Only
Rate Limits
Rate limits are enforced per API key:
- Standard endpoints: 100 requests/minute
- Bulk endpoints: 10 requests/minute
When rate limited, the API returns 429 Too Many Requests. Retry after the Retry-After header value.
Errors
All errors follow a consistent format:
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Phone number is required"
}
}HTTP status codes:
401 UNAUTHORIZED — Invalid or missing API key
403 READ_ONLY / TEST_KEY — Insufficient permissions
404 NOT_FOUND — Resource doesn't exist
409 DUPLICATE — Contact with this phone exists
429 RATE_LIMIT_EXCEEDED — Too many requests
500 INTERNAL_ERROR — Server error
Pagination
List endpoints use cursor-based pagination. Pass limit (max 100, default 50) and cursor (from previous response).
{
"success": true,
"data": [...],
"meta": {
"limit": 50,
"next_cursor": "abc123",
"has_more": true
}
}Pass meta.next_cursor as the cursor query parameter to fetch the next page.
Contacts API
/api/v1/contactsCreate a single contact
/api/v1/contacts/bulkCreate up to 1,000 contacts
/api/v1/contactsList contacts (paginated)
/api/v1/contacts/:idGet a single contact
/api/v1/contacts/:idUpdate a contact
/api/v1/contacts/:idDelete a contact
/api/v1/contacts/:id/dncAdd to Do Not Call list
/api/v1/contacts/:id/dncRemove from Do Not Call list
Create Contact
curl -X POST https://phonestack.com/api/v1/contacts \
-H "Authorization: Bearer ps_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"first_name": "John",
"last_name": "Martinez",
"phone": "+12145551234",
"email": "john@example.com",
"company": "ABC Corp",
"tags": ["website_lead"],
"campaign_id": "CAMPAIGN_ID",
"custom_fields": {"source": "contact_form"}
}'If campaign_id is provided and the campaign is running, the contact is immediately injected into the call queue.
Bulk Create
curl -X POST https://phonestack.com/api/v1/contacts/bulk \
-H "Authorization: Bearer ps_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"contacts": [
{"first_name": "John", "phone": "+12145551234"},
{"first_name": "Jane", "phone": "+13105559876"}
],
"campaign_id": "CAMPAIGN_ID",
"tags": ["batch_import"]
}'Returns: {"created": 2, "duplicates": 0, "invalid": 0}
List Contacts
Filters: dnc_status, source, tag
curl "https://phonestack.com/api/v1/contacts?tag=website_lead&limit=20" \
-H "Authorization: Bearer ps_live_YOUR_KEY"Calls API
/api/v1/callsList calls with filters
/api/v1/calls/:idGet full call detail
/api/v1/calls/:id/transcriptGet transcript only
/api/v1/calls/:id/recordingGet signed recording URL (expires in 1 hour)
List Calls
Filters: campaign_id, disposition, status, direction, contact_id, since, until, min_duration, has_meeting
Include related data: ?include=transcript,contact
curl "https://phonestack.com/api/v1/calls?campaign_id=CAMP_ID&disposition=hot_lead&include=transcript" \
-H "Authorization: Bearer ps_live_YOUR_KEY"Call Response
{
"id": "call_xyz",
"campaign_id": "camp_abc",
"contact_id": "ct_123",
"status": "completed",
"direction": "outbound",
"from_number": "+12145550123",
"to_number": "+12145551234",
"transferred_to": null,
"talk_duration_seconds": 154,
"cost_cents": 63,
"cost_breakdown": {"twilio": 23, "gemini": 35, "claude": 5},
"analysis": {
"disposition": "hot_lead",
"sentiment": "positive",
"summary": "Interested in consultation...",
"meeting_booked": true,
"ai_confidence_score": 92
},
"tool_executions": [
{"tool": "check_availability", "status": "success"},
{"tool": "request_meeting", "status": "success"}
]
}Campaigns API
/api/v1/campaignsList campaigns
/api/v1/campaigns/:idGet campaign with stats
/api/v1/campaigns/:id/startStart or activate a campaign
/api/v1/campaigns/:id/pausePause a running campaign
/api/v1/campaigns/:id/resumeResume a paused campaign
/api/v1/campaigns/:id/stopStop and complete a campaign
Filters: direction (outbound/inbound), status
Campaign Control
# Start a campaign
curl -X POST https://phonestack.com/api/v1/campaigns/CAMP_ID/start \
-H "Authorization: Bearer ps_live_YOUR_KEY"
# Pause a campaign
curl -X POST https://phonestack.com/api/v1/campaigns/CAMP_ID/pause \
-H "Authorization: Bearer ps_live_YOUR_KEY"Campaigns must be created in the dashboard. The API controls start/pause/resume/stop only.
Custom Fields API
Define reusable field definitions at the org level. Custom fields enforce types and enum options on contacts and call extraction. Bind them to profiles so every call auto-extracts the same structured data.
/api/v1/custom-fieldsList all custom field definitions (filterable by category)
/api/v1/custom-fieldsCreate a new custom field definition
/api/v1/custom-fields/:idGet a single custom field definition
/api/v1/custom-fields/:idUpdate a custom field definition
/api/v1/custom-fields/:idDelete a custom field definition
Create Custom Field
curl -X POST https://phonestack.com/api/v1/custom-fields \
-H "Authorization: Bearer ps_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "budget_range",
"type": "enum",
"description": "Prospect budget range for the project",
"category": "both",
"required": false,
"options": ["under_10k", "10k_50k", "50k_100k", "100k_plus"]
}'Field types:
string— Free textnumber— Numeric valuesboolean— true/falsedate— ISO date stringsenum— One of a defined set of options
Categories:
contact— Only for contact recordsextraction— Only for call data extractionboth— Usable on contacts and extraction (default)
Validation
When custom fields are defined, the API validates custom_fields on contact create/update and trigger_call requests. Enum values must match the defined options. Required fields must be present. Unknown fields are allowed (for flexibility) but defined fields are type-checked.
Profiles API
Manage AI agent profiles programmatically. Profiles control the AI caller's persona, opener, guardrails, and extraction behavior.
/api/v1/profilesCreate and auto-compile a new profile
/api/v1/profilesList active profiles
/api/v1/profiles/:idGet full profile details
/api/v1/profiles/:idUpdate and re-compile a profile
/api/v1/profiles/:idArchive (soft-delete) a profile
Profile Extract Fields
Bind custom field definitions to a profile so every call using that profile automatically extracts those fields — no need to pass extract on each trigger_call request.
# First, create your custom fields
curl -X POST https://phonestack.com/api/v1/custom-fields \
-H "Authorization: Bearer ps_live_YOUR_KEY" \
-d '{"name": "budget_range", "type": "enum", "description": "Budget", "options": ["under_10k", "10k_50k", "50k_plus"]}'
curl -X POST https://phonestack.com/api/v1/custom-fields \
-H "Authorization: Bearer ps_live_YOUR_KEY" \
-d '{"name": "decision_timeline", "type": "string", "description": "When they plan to make a decision"}'
# Then bind them to a profile
curl -X PATCH https://phonestack.com/api/v1/profiles/PROFILE_ID \
-H "Authorization: Bearer ps_live_YOUR_KEY" \
-d '{"extract_fields": ["budget_range", "decision_timeline"]}'
# Now every call using this profile auto-extracts both fields
curl -X POST https://phonestack.com/api/v1/calls/trigger \
-H "Authorization: Bearer ps_live_YOUR_KEY" \
-d '{"profile_id": "PROFILE_ID", "phone": "+14155551234"}'If you also pass extract on trigger_call, it overrides the profile's extract_fields for that specific call.
Reports API
/api/v1/reportsList saved reports
/api/v1/reports/:idGet report configuration
/api/v1/reports/:id/executeRun a report
/api/v1/reports/:id/export?format=csvExport as CSV
/api/v1/reports/:id/export?format=pdfExport as PDF
Execute Report
curl -X POST https://phonestack.com/api/v1/reports/REPORT_ID/execute \
-H "Authorization: Bearer ps_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"date_range": "last_7_days"}'Webhooks
Configure webhooks in your Phone Stack dashboard. Phone Stack sends POST requests to your URL when events occur.
Events
call.completed — Call finished with analysis
call.started — Call placed or received
call.failed — Call failed
call.voicemail — Went to voicemail
campaign.started — Campaign launched
campaign.paused — Campaign paused
campaign.completed — Campaign finished
contact.created — New contact added
contact.dnc_requested — Do Not Call request
billing.limit_warning — 80% of spending limit reached
billing.limit_reached — Spending limit hit
Signature Verification
Each webhook includes an X-PhoneStack-Signature header — an HMAC-SHA256 of the request body using your webhook secret.
const crypto = require("crypto");
function verifyWebhook(body, signature, secret) {
const expected = crypto
.createHmac("sha256", secret)
.update(body)
.digest("hex");
return signature === expected;
}
// In your webhook handler:
app.post("/webhooks/phonestack", (req, res) => {
const sig = req.headers["x-phonestack-signature"];
if (!verifyWebhook(JSON.stringify(req.body), sig, WEBHOOK_SECRET)) {
return res.status(401).send("Invalid signature");
}
const { event, data } = req.body;
console.log(`Event: ${event}`, data);
res.status(200).send("OK");
});Webhook Payload Example: call.completed
{
"event": "call.completed",
"timestamp": "2025-04-03T14:34:12Z",
"organization_id": "org_abc123",
"data": {
"call_id": "call_xyz",
"campaign_id": "camp_def",
"direction": "outbound",
"talk_duration_seconds": 154,
"cost_cents": 63,
"analysis": {
"disposition": "hot_lead",
"summary": "Interested in consultation...",
"meeting_booked": true
},
"transcript": [
{"speaker": "agent", "text": "Hi John...", "timestamp_seconds": 0}
]
}
}Webhooks auto-disable after 10 consecutive delivery failures. Fix your endpoint and re-enable in your dashboard.
MCP Server
Phone Stack ships an MCP (Model Context Protocol) server at mcp.phonestack.com with 40 tools across 8 categories. Connect it to ChatGPT, Claude, or any MCP-compatible AI client to manage your call operation conversationally.
Available Tools (40)
Calls (7)
trigger_call, list_calls, get_recording, get_transcript, trigger_batch, get_batch_status, get_batch_results
Profiles (5)
create_profile, list_profiles, get_profile, update_profile, archive_profile
Contacts (8)
list_contacts, get_contact, create_contact, update_contact, delete_contact, bulk_create_contacts, add_to_dnc, remove_from_dnc
Campaigns (6)
list_campaigns, get_campaign, start_campaign, stop_campaign, pause_campaign, resume_campaign
Reports (4)
list_reports, get_report, execute_report, export_report
Webhooks (4)
list_webhooks, subscribe_webhook, unsubscribe_webhook, get_webhook_sample
Custom Fields (4)
list_custom_fields, create_custom_field, update_custom_field, delete_custom_field
Discovery (2)
help, suggest_options
Discovery Tools
Two special tools help AI assistants use the API correctly:
help
Maps natural language intents to the right tool and field. Say “greeting” and it tells you to use update_profile with the opener field. Covers 50+ intent synonyms across all categories.
suggest_options
Shows existing resources before creating new ones — prevents duplicate custom fields, finds the right profile ID, lists available voices and webhook events. Searchable by keyword.
Connect to ChatGPT
In ChatGPT, go to Settings → Connected Apps → Add App and enter the MCP server URL. Authenticate with your Phone Stack account.
MCP Server URL: https://mcp.phonestack.com/mcpConnect to Claude
In Claude, go to Settings → Connectors → Add Connector and enter the MCP server URL. Authenticate with your Phone Stack account.
MCP Server URL: https://mcp.phonestack.com/mcpExample Conversations
“Call +14155551234 and ask for their budget and timeline”
“Show me all calls from last week where a meeting was booked”
“Create a custom field called budget_range with options under 10k, 10-50k, and 50k+”
“Pause the outbound sales campaign”
“Add a budget_range extraction field to my Sales Qualifier profile”
Integration Recipes
Website Form → Instant Call
A lead fills out your form, Phone Stack calls them within seconds:
// On your website form submit
async function onFormSubmit(formData) {
await fetch("https://phonestack.com/api/v1/contacts", {
method: "POST",
headers: {
"Authorization": "Bearer ps_live_YOUR_KEY",
"Content-Type": "application/json"
},
body: JSON.stringify({
first_name: formData.name,
phone: formData.phone,
email: formData.email,
campaign_id: "YOUR_CAMPAIGN_ID",
tags: ["website_lead"],
custom_fields: { source_page: window.location.pathname }
})
});
}CRM Sync (Python)
import requests
API_KEY = "ps_live_YOUR_KEY"
CAMPAIGN_ID = "YOUR_CAMPAIGN_ID"
def sync_new_leads(crm_leads):
contacts = [
{
"first_name": lead["first_name"],
"last_name": lead["last_name"],
"phone": lead["phone"],
"email": lead["email"],
"custom_fields": {"crm_id": lead["id"]},
}
for lead in crm_leads
]
resp = requests.post(
"https://phonestack.com/api/v1/contacts/bulk",
headers={"Authorization": f"Bearer {API_KEY}"},
json={"contacts": contacts, "campaign_id": CAMPAIGN_ID}
)
print(f"Synced {resp.json()['data']['created']} leads")Webhook → CRM Update
// Receive call results and update your CRM
app.post("/webhooks/phonestack", (req, res) => {
const { event, data } = req.body;
if (event === "call.completed") {
updateCRM(data.contact_id, {
last_call: data.timestamp,
disposition: data.analysis.disposition,
summary: data.analysis.summary,
meeting_booked: data.analysis.meeting_booked,
});
if (data.analysis.disposition === "hot_lead") {
notifySalesTeam(data);
}
}
res.status(200).send("OK");
});No-Code (Zapier / Make.com)
Phone Stack webhooks work natively with Zapier, Make.com, and n8n. Set your webhook URL to the Zapier/Make catch hook, select events, and build automations — push call results to Google Sheets, send Slack notifications for hot leads, create calendar events for booked meetings.
Ready to get started?
Create a free Phone Stack account to get your API keys and start integrating.