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.

Base URL: https://phonestack.com/api/v1Format: JSONAuth: Bearer token

Authentication

All API requests require a Bearer token in the Authorization header. Create API keys in your Phone Stack dashboard.

bash
curl https://phonestack.com/api/v1/calls \
  -H "Authorization: Bearer ps_live_YOUR_API_KEY"

Key prefixes:

  • ps_live_ — Production keys with full access
  • ps_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:

json
{
  "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).

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

POST
/api/v1/contacts

Create a single contact

POST
/api/v1/contacts/bulk

Create up to 1,000 contacts

GET
/api/v1/contacts

List contacts (paginated)

GET
/api/v1/contacts/:id

Get a single contact

PATCH
/api/v1/contacts/:id

Update a contact

DELETE
/api/v1/contacts/:id

Delete a contact

POST
/api/v1/contacts/:id/dnc

Add to Do Not Call list

DELETE
/api/v1/contacts/:id/dnc

Remove from Do Not Call list

Create Contact

bash
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

bash
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

bash
curl "https://phonestack.com/api/v1/contacts?tag=website_lead&limit=20" \
  -H "Authorization: Bearer ps_live_YOUR_KEY"

Calls API

GET
/api/v1/calls

List calls with filters

GET
/api/v1/calls/:id

Get full call detail

GET
/api/v1/calls/:id/transcript

Get transcript only

GET
/api/v1/calls/:id/recording

Get 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

bash
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

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

GET
/api/v1/campaigns

List campaigns

GET
/api/v1/campaigns/:id

Get campaign with stats

POST
/api/v1/campaigns/:id/start

Start or activate a campaign

POST
/api/v1/campaigns/:id/pause

Pause a running campaign

POST
/api/v1/campaigns/:id/resume

Resume a paused campaign

POST
/api/v1/campaigns/:id/stop

Stop and complete a campaign

Filters: direction (outbound/inbound), status

Campaign Control

bash
# 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.

GET
/api/v1/custom-fields

List all custom field definitions (filterable by category)

POST
/api/v1/custom-fields

Create a new custom field definition

GET
/api/v1/custom-fields/:id

Get a single custom field definition

PATCH
/api/v1/custom-fields/:id

Update a custom field definition

DELETE
/api/v1/custom-fields/:id

Delete a custom field definition

Create Custom Field

bash
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 text
  • number — Numeric values
  • boolean — true/false
  • date — ISO date strings
  • enum — One of a defined set of options

Categories:

  • contact — Only for contact records
  • extraction — Only for call data extraction
  • both — 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.

POST
/api/v1/profiles

Create and auto-compile a new profile

GET
/api/v1/profiles

List active profiles

GET
/api/v1/profiles/:id

Get full profile details

PATCH
/api/v1/profiles/:id

Update and re-compile a profile

DELETE
/api/v1/profiles/:id

Archive (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.

bash
# 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

GET
/api/v1/reports

List saved reports

GET
/api/v1/reports/:id

Get report configuration

POST
/api/v1/reports/:id/execute

Run a report

GET
/api/v1/reports/:id/export?format=csv

Export as CSV

GET
/api/v1/reports/:id/export?format=pdf

Export as PDF

Execute Report

bash
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.

javascript
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

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

text
MCP Server URL: https://mcp.phonestack.com/mcp

Connect to Claude

In Claude, go to Settings → Connectors → Add Connector and enter the MCP server URL. Authenticate with your Phone Stack account.

text
MCP Server URL: https://mcp.phonestack.com/mcp

Example 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:

javascript
// 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)

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

javascript
// 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.