API Reference
REST API
Complete reference for the Epoch Voice AI REST API. 73 endpoints across 15 categories.
https://voice.epochdm.comAuthentication
POST /api/auth/[...nextauth] (credentials login).x-admin-key header matching ADMIN_API_KEY env var.Authorization: Bearer <CRON_SECRET> header.ept_...) in the request body.Authentication5 endpoints
/api/auth/registerPublicCreate a new user account and organization.
{
"name": "Jane Smith",
"email": "jane@example.com",
"password": "securepass123",
"organizationName": "Acme Corp"
}{
"message": "Registration successful",
"organizationId": "clx...",
"userId": "clx..."
}Password must be at least 8 characters. Sends a welcome email on success.
/api/auth/forgot-passwordPublicRequest a password reset link via email.
{ "email": "jane@example.com" }{ "message": "If an account with that email exists, a reset link has been sent." }Always returns success to prevent email enumeration. Reset token expires in 1 hour.
/api/auth/reset-passwordPublicComplete a password reset using the emailed token.
{
"token": "reset_token_from_email",
"password": "newSecurePass456"
}{ "message": "Password has been reset successfully." }/api/auth/passwordSessionChange the current user's password.
{
"currentPassword": "oldPass",
"newPassword": "newPass",
"forced": false
}{ "success": true }Set forced=true for admin-required password changes (skips current password check).
/api/auth/magic-linkPublicAuthenticate via a one-time magic link token. Redirects to dashboard on success.
?token=<magic_link_token>Redirect to /dashboard (sets session cookie)Usage & Billing7 endpoints
/api/usageSessionGet the organization's usage snapshot, plan details, call history, and feature flags.
?days=30 (optional, default 30){
"snapshot": {
"minutesUsed": 142.5,
"minutesLimit": 500,
"percentUsed": 28.5,
"currentCycleStart": "2026-03-01T00:00:00Z",
"currentCycleEnd": "2026-03-31T23:59:59Z"
},
"plan": "PRO",
"phoneNumber": "+15551234567",
"assistant": { "id": "...", "name": "Reception AI" },
"features": {
"transcripts": true,
"analytics": true,
"outbound": true
},
"history": [...],
"recentCalls": [...]
}/api/billing/historySessionGet overage billing records (up to 52 weeks) and current unbilled overage.
{
"records": [
{
"id": "...",
"weekStart": "2026-03-03",
"weekEnd": "2026-03-09",
"overageMinutes": 12.5,
"amountCents": 375,
"status": "PAID"
}
],
"currentOverage": {
"totalOverageMinutes": 5.2,
"unbilledMinutes": 5.2,
"estimatedCostCents": 156,
"overageEnabled": true,
"rateCentsPerMin": 30
}
}/api/stripe/checkoutSessionCreate a Stripe checkout session for plan upgrade.
{ "planTier": "PRO" }{ "url": "https://checkout.stripe.com/..." }Valid tiers: STARTER, ADVANCED, PRO. Returns 400 if attempting a downgrade (use portal instead).
/api/stripe/portalSessionGet a link to the Stripe customer billing portal for managing subscriptions.
{ "returnPath": "/dashboard/billing" }{ "url": "https://billing.stripe.com/..." }/api/subscription/cancelSessionCancel the current subscription (takes effect at period end).
{ "acknowledged": true }{
"success": true,
"cancelledAt": "2026-03-13T...",
"effectiveAt": "2026-04-01T...",
"deletionAt": "2026-04-08T..."
}Returns 409 if already cancelled.
/api/subscription/reactivateSessionReverse a pending cancellation before the effective date.
{}{ "success": true }/api/subscription/statusSessionCheck current subscription and cancellation status.
{
"plan": "PRO",
"status": "active",
"cancelledAt": null,
"effectiveAt": null,
"stripeSubscriptionId": "sub_..."
}Assistants8 endpoints
/api/assistantsSessionList all assistants for the organization with call counts.
{
"assistants": [
{
"id": "...",
"vapiAssistantId": "...",
"name": "Reception AI",
"status": "ACTIVE",
"callCount": 47
}
],
"phoneNumber": "+15551234567"
}/api/assistantsSessionRegister a new assistant.
{
"vapiAssistantId": "vapi_...",
"name": "Reception AI"
}{ "assistant": { "id": "...", "name": "Reception AI", ... } }Enforces plan limits on assistant count. Returns 409 if VAPI assistant ID already registered.
/api/assistants/[id]SessionUpdate an assistant's display name.
{ "name": "New Name" }{ "assistant": { "id": "...", "name": "New Name", ... } }/api/assistants/[id]SessionDelete an assistant.
{ "success": true }/api/assistant-nameSessionGet the assistant's display name and whether the user can edit it.
{
"name": "Reception AI",
"defaultName": "Savannah",
"canEdit": true
}canEdit is true on Pro plan and above.
/api/assistant-nameSessionPro+Update the assistant's display name. Propagates to greeting and system prompt.
{ "name": "Alex" }{ "success": true }Name must be 2-30 characters.
/api/first-message-modeSessionGet the assistant's greeting mode configuration.
{
"mode": "assistant-speaks-first",
"firstMessage": "Hi! Thanks for calling Acme Corp. How can I help you today?"
}/api/first-message-modeSessionUpdate the greeting mode.
{
"mode": "assistant-speaks-first",
"firstMessage": "Welcome! How can I help?"
}{ "success": true }Valid modes: assistant-speaks-first, assistant-waits-for-user, assistant-speaks-first-with-model-generated-message
Voice & Prompt6 endpoints
/api/voiceSessionGet the current voice configuration.
{
"provider": "elevenlabs",
"voiceId": "pNInz6obpgDQGcFmaJgB",
"voiceName": "Savannah",
"canEdit": true
}canEdit is true on Advanced plan and above.
/api/voiceSessionAdvanced+Change the assistant's voice. Regenerates persona section in system prompt.
{
"voiceId": "pNInz6obpgDQGcFmaJgB",
"voiceName": "Savannah"
}{ "success": true }/api/voice/catalogPublicList all available voices with preview URLs. Cached for 24 hours.
[
{
"id": "pNInz6obpgDQGcFmaJgB",
"name": "Savannah",
"gender": "female",
"desc": "Warm, professional female voice",
"previewUrl": "https://..."
}
]/api/promptSessionGet the full system prompt and editing permissions.
{
"systemPrompt": "You are a friendly AI receptionist...",
"assistantName": "Reception AI",
"vapiAssistantId": "...",
"canEdit": true
}canEdit is true on Advanced plan and above.
/api/promptSessionAdvanced+Overwrite the entire system prompt.
{ "systemPrompt": "You are a friendly AI receptionist..." }{ "success": true }Inline tools are preserved automatically.
/api/prompt/regenerateSessionAdvanced+AI-regenerate specific sections of the system prompt based on business data.
{ "sections": ["persona", "context"] }{
"success": true,
"systemPrompt": "...",
"regenerated": ["persona", "context"]
}Defaults to both sections if not specified.
Knowledge Base4 endpoints
/api/kbSessionList all knowledge base sections for the active assistant.
{
"assistantId": "...",
"vapiAssistantId": "...",
"canEdit": true,
"sections": [
{
"key": "faqs",
"label": "FAQs",
"text": "...",
"vapiFileId": "...",
"updatedAt": "..."
}
]
}canEdit is true on Starter plan and above.
/api/kbSessionStarter+Create or update a knowledge base section. Generates a Markdown file and uploads to VAPI.
{
"sectionKey": "faqs",
"text": "Q: What are your hours?\nA: We're open Mon-Fri 9am-5pm."
}{
"success": true,
"section": {
"key": "faqs",
"vapiFileId": "...",
"fileName": "faqs.md"
}
}/api/kbSessionStarter+Remove a knowledge base section.
?section=faqs{ "success": true }/api/kb/uploadSessionUpload a file (PDF, DOCX, CSV, TXT) to use as a knowledge base source.
multipart/form-data:
file: <binary>
section: "company-info"{
"fileId": "...",
"fileName": "handbook.pdf",
"fileSize": 245000
}Max file size: 10MB. Accepted types: PDF, DOCX, CSV, TXT.
Calls4 endpoints
/api/calls/[id]SessionGet details for a specific call including transcript (if available on plan).
{
"call": {
"id": "...",
"vapiCallId": "...",
"status": "COMPLETED",
"startedAt": "2026-03-13T10:00:00Z",
"endedAt": "2026-03-13T10:05:30Z",
"durationMinutes": 5.5,
"customerNumber": "+15559876543",
"transcript": "...",
"summary": "..."
},
"plan": "PRO",
"features": { "transcripts": true, "recordings": true }
}Transcripts and recordings are gated by plan tier.
/api/calls/browser-tokenSessionEnterpriseGet a Twilio Client access token for browser-based calling from the dashboard.
{
"token": "eyJ...",
"identity": "org_...:user_...",
"phoneNumber": "+15551234567",
"expiresIn": 3600
}/api/calls/outboundSessionEnterpriseInitiate an AI-powered outbound call to a customer.
{ "customerNumber": "+15559876543" }{
"success": true,
"call": {
"id": "...",
"status": "queued",
"customerNumber": "+15559876543"
}
}Validates E.164 phone format. Checks available minutes before dialing.
/api/calls/outbound/statusSessionPoll the status of an outbound call.
?vapiCallId=<string>{
"id": "...",
"status": "in-progress",
"startedAt": "2026-03-13T10:00:00Z",
"endedAt": null,
"minutes": 2.3,
"endReason": null
}Returns 404 if the call hasn't been recorded by the webhook yet.
SMS2 endpoints
/api/sms/sendSessionPro+Send an SMS message from your business phone number.
{
"to": "+15559876543",
"body": "Hi! Your appointment is confirmed for tomorrow at 2pm."
}{
"success": true,
"messageSid": "SM...",
"status": "queued"
}Requires active A2P registration (CAMPAIGN_APPROVED status).
/api/sms/logsSessionGet paginated SMS message history.
?page=1&limit=20&direction=INBOUND{
"logs": [
{
"id": "...",
"direction": "INBOUND",
"from": "+15559876543",
"to": "+15551234567",
"body": "Hello!",
"status": "received",
"createdAt": "..."
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 156,
"totalPages": 8
}
}Phone Numbers5 endpoints
/api/phone-numbersSessionGet the organization's provisioned phone number and SIP trunk details.
{
"phoneNumber": "+15551234567",
"vapiPhoneNumberId": "...",
"twilioPhoneNumberSid": "PN...",
"hasSipTrunk": true,
"hasAssistant": true,
"assistantName": "Reception AI"
}/api/phone-numbersSessionProvision a new phone number via Twilio SIP trunking.
{ "areaCode": "415" }{
"success": true,
"phoneNumber": "+14155551234",
"vapiPhoneNumberId": "...",
"twilioPhoneNumberSid": "PN..."
}Returns 409 if already provisioned. Requires an active assistant.
/api/phone-numbers/searchSessionSearch available phone numbers by area code or locality.
?areaCode=415&locality=San+Francisco&limit=5{
"numbers": [
{ "phoneNumber": "+14155551234", "locality": "San Francisco", "region": "CA" }
]
}/api/transfer-numberSessionGet the configured call transfer number.
{ "transferNumber": "+15559876543", "canEdit": true }canEdit is true on Advanced plan and above.
/api/transfer-numberSessionAdvanced+Set or clear the call transfer number. Updates inline tools and handling instructions.
{ "transferNumber": "+15559876543" }{ "success": true, "transferNumber": "+15559876543" }Normalizes to E.164. Omit transferNumber to clear.
Business Settings6 endpoints
/api/business-infoSessionGet the organization's business type, description, and website URL.
{
"businessType": "dental_office",
"businessDescription": "Family dental practice...",
"websiteUrl": "https://acmedental.com"
}/api/business-infoSessionUpdate business info. Surgically updates the system prompt and syncs to VAPI.
{
"businessType": "dental_office",
"businessDescription": "Family dental practice...",
"websiteUrl": "https://acmedental.com"
}{ "success": true, "businessType": "dental_office", ... }/api/business-hoursSessionGet the organization's business hours and timezone.
{
"businessHours": {
"monday": { "open": "09:00", "close": "17:00", "closed": false },
"tuesday": { "open": "09:00", "close": "17:00", "closed": false }
},
"timezone": "America/Los_Angeles"
}/api/business-hoursSessionUpdate business hours. Surgically updates the hours section in the system prompt.
{
"businessHours": { ... },
"timezone": "America/Los_Angeles"
}{ "success": true, "businessHours": { ... }, "timezone": "..." }/api/business-addressSessionGet the organization's business address.
{ "businessAddress": "123 Main St, Suite 100, San Francisco, CA 94102" }/api/business-addressSessionUpdate business address. Surgically updates the address in the system prompt.
{ "businessAddress": "123 Main St, Suite 100, San Francisco, CA 94102" }{ "success": true, "businessAddress": "..." }Calendar Integration4 endpoints
/api/calendar/connectSessionAdvanced+Connect a Cal.com calendar for AI-powered appointment booking.
{
"provider": "CAL_COM",
"apiKey": "cal_live_...",
"eventTypeId": 12345
}{ "success": true, "provider": "CAL_COM" }Validates the API key, creates function tools on the VAPI assistant, and updates the system prompt.
/api/calendar/statusSessionCheck the current calendar integration status.
{
"connected": true,
"provider": "CAL_COM",
"config": { "eventTypeId": 12345 },
"connectedAt": "2026-03-01T...",
"canEdit": true
}/api/calendar/disconnectSessionDisconnect the calendar integration. Removes tools from VAPI and updates the prompt.
{}{ "success": true }/api/calendar/testSessionRun a diagnostic test on the calendar integration (org lookup, credential decryption, API connectivity).
{
"ok": true,
"timestamp": "2026-03-13T...",
"steps": [
{ "name": "org_lookup", "ok": true },
{ "name": "plan_check", "ok": true },
{ "name": "credential_decrypt", "ok": true },
{ "name": "cal_com_api", "ok": true }
]
}Widget & Embed Tokens4 endpoints
/api/widget/tokenEmbed TokenExchange an embed token for a short-lived Twilio access token. Used by the widget internally.
{ "embedToken": "ept_abc123..." }{
"token": "eyJ...",
"identity": "org_...:widget_...",
"phoneNumber": "+15551234567",
"orgName": "Acme Corp",
"expiresIn": 3600
}CORS-enabled. Validates domain whitelist. Token TTL is 1 hour with auto-refresh.
/api/embed-tokenSessionPro+List all active embed tokens for the organization.
{
"tokens": [
{
"id": "...",
"label": "Main Website",
"tokenPrefix": "ept_abc1...",
"allowedDomains": ["mysite.com"],
"lastUsedAt": "2026-03-12T...",
"createdAt": "2026-02-15T..."
}
]
}/api/embed-tokenSessionPro+Generate a new embed token. The raw token is returned only once.
{
"label": "HubSpot CRM",
"allowedDomains": ["app.hubspot.com"]
}{
"token": "ept_a1b2c3d4e5f6...",
"prefix": "ept_a1b2c3d4",
"label": "HubSpot CRM"
}Copy the token immediately — it cannot be retrieved again.
/api/embed-tokenSessionRevoke an embed token. Takes effect immediately.
?id=<token_id>{ "revoked": true }A2P Compliance (SMS Registration)4 endpoints
/api/a2p/statusSessionCheck A2P (Application-to-Person) SMS registration status.
{
"status": "CAMPAIGN_APPROVED",
"paid": true,
"brandType": "SOLE_PROPRIETOR",
"messagingServiceSid": "MG...",
"smsUsed": 42,
"hasSmsSubscription": true
}/api/a2p/initiateSessionPro+Start or retry A2P brand registration with Twilio.
{}{ "success": true, "status": "BRAND_PENDING" }Only allowed if status is NOT_STARTED or FAILED. Requires prior payment via /api/a2p/checkout.
/api/a2p/checkoutSessionPro+Create a Stripe checkout session for A2P registration fee.
{
"businessInfo": {
"businessName": "Acme Corp",
"businessType": "SOLE_PROPRIETOR",
"ein": "12-3456789"
},
"representativeInfo": {
"firstName": "Jane",
"lastName": "Smith",
"email": "jane@acme.com",
"phone": "+15551234567"
},
"campaignInfo": {
"useCase": "appointment_reminders",
"sampleMessages": ["Your appointment is tomorrow at 2pm."]
}
}{ "url": "https://checkout.stripe.com/..." }/api/a2p/campaignSessionPro+Submit an SMS campaign for approval after brand is approved.
{ "campaignInfo": { ... } }{
"success": true,
"campaignSid": "QE...",
"status": "CAMPAIGN_PENDING"
}Requires brand status to be BRAND_APPROVED first.
Onboarding3 endpoints
/api/onboardingSessionCheck onboarding status and retrieve saved data.
{
"completed": false,
"completedAt": null,
"data": { "businessName": "Acme Corp", "businessType": "dental_office", ... },
"phoneNumber": null,
"organizationName": "Acme Corp"
}/api/onboardingSessionSave onboarding data. Set complete=true to trigger provisioning pipeline.
{
"onboardingData": {
"businessName": "Acme Corp",
"businessType": "dental_office",
"websiteUrl": "https://acmedental.com",
"voiceId": "pNInz6obpgDQGcFmaJgB"
},
"complete": true
}{
"success": true,
"completed": true,
"phoneNumber": "+14155551234",
"provisioningErrors": []
}Returns 400 if onboarding was already completed.
/api/onboarding/analyzeSessionAI-analyze a website URL to extract business data for onboarding.
{ "url": "https://acmedental.com" }{
"extractedData": {
"businessName": "Acme Dental",
"businessType": "dental_office",
"description": "Family dental practice...",
"hours": { ... },
"address": "123 Main St..."
},
"businessName": "Acme Dental",
"pagesScraped": 3,
"scrapeWarnings": []
}Only available during onboarding (before completion). Uses Claude AI for analysis.
Webhooks (Incoming)6 endpoints
/api/webhooks/vapiPublicReceives call events from VAPI (call started, ended, transcript, tool calls). Core webhook for minutes tracking and call logging.
{ "message": { "type": "end-of-call-report", ... } }200 OK (always)Handles events: call.started, call.ended, end-of-call-report, tool-calls (calendar). Always returns 200.
/api/webhooks/stripePublicReceives Stripe payment events (invoice paid, subscription changes).
Stripe webhook payload (signature verified)200 OKVerified via STRIPE_WEBHOOK_SECRET signature.
/api/webhooks/twilio/voicePublicTwilio voice webhook — generates TwiML for incoming/outgoing browser calls.
Twilio webhook form dataTwiML XML/api/webhooks/twilio/statusPublicReceives Twilio call status updates (ringing, in-progress, completed).
Twilio webhook form data200 OK/api/webhooks/twilio/sms-inboundPublicReceives inbound SMS messages from Twilio.
Twilio webhook form dataTwiML XML/api/webhooks/twilio/sms-statusPublicReceives SMS delivery status updates from Twilio.
Twilio webhook form data200 OKCron Jobs5 endpoints
/api/cron/billing-resetCron SecretDaily cycle reset — resets monthly minute counters at billing period boundaries.
{ "reset": 12, "skipped": 450, "errors": 0 }/api/cron/sync-callsCron SecretDaily sync — pulls latest call data from VAPI for all organizations.
{ "synced": 85, "errors": 0 }/api/cron/overage-billingCron SecretWeekly (Monday 6am EST) — bills organizations for overage minutes used in the past week.
{ "billed": 3, "skipped": 40, "errors": 0 }/api/cron/payment-retryCron SecretDaily — retries failed overage payments.
{ "retried": 1, "succeeded": 1, "failed": 0 }/api/cron/a2p-statusCron SecretDaily — checks and updates A2P registration statuses with Twilio.
{ "checked": 5, "updated": 1 }Machine-Readable Version
The full API reference is also available as structured Markdown for LLMs and automated tools.
---
title: Epoch Voice AI — REST API Reference
version: 1.0
last_updated: 2026-03-13
base_url: https://voice.epochdm.com
url: https://voice.epochdm.com/docs/api
raw_markdown_url: https://voice.epochdm.com/docs/api/llm.md
---
# Epoch Voice AI — REST API Reference
Base URL: `https://voice.epochdm.com`
## Authentication Methods
| Method | Description |
|--------|-------------|
| **Session** | NextAuth session cookie via `POST /api/auth/[...nextauth]` credentials login |
| **Admin** | `x-admin-key` header matching `ADMIN_API_KEY` env var |
| **Cron** | `Authorization: Bearer <CRON_SECRET>` header |
| **Embed Token** | `ept_...` token in request body (widget token exchange only) |
| **None** | Public endpoint, no auth required |
---
## Authentication
### POST /api/auth/register
- **Auth:** None
- **Body:** `{ name, email, password, organizationName }`
- **Response:** `{ message, organizationId, userId }`
- **Notes:** Password min 8 chars. Sends welcome email.
### POST /api/auth/forgot-password
- **Auth:** None
- **Body:** `{ email }`
- **Response:** `{ message }` (always succeeds to prevent enumeration)
### POST /api/auth/reset-password
- **Auth:** None
- **Body:** `{ token, password }`
- **Response:** `{ message }`
### PUT /api/auth/password
- **Auth:** Session
- **Body:** `{ currentPassword, newPassword, forced? }`
- **Response:** `{ success: true }`
### GET /api/auth/magic-link?token=...
- **Auth:** None (token-based)
- **Response:** Redirect to /dashboard with session cookie
---
## Usage & Billing
### GET /api/usage
- **Auth:** Session
- **Query:** `?days=30`
- **Response:** `{ snapshot: { minutesUsed, minutesLimit, percentUsed, currentCycleStart, currentCycleEnd }, plan, phoneNumber, assistant, features, history, recentCalls }`
### GET /api/billing/history
- **Auth:** Session
- **Response:** `{ records: [{ weekStart, weekEnd, overageMinutes, amountCents, status }], currentOverage: { totalOverageMinutes, unbilledMinutes, estimatedCostCents, overageEnabled, rateCentsPerMin } }`
### POST /api/stripe/checkout
- **Auth:** Session
- **Body:** `{ planTier: "STARTER" | "ADVANCED" | "PRO" }`
- **Response:** `{ url }`
### POST /api/stripe/portal
- **Auth:** Session
- **Body:** `{ returnPath? }`
- **Response:** `{ url }`
### POST /api/subscription/cancel
- **Auth:** Session
- **Body:** `{ acknowledged: true }`
- **Response:** `{ success, cancelledAt, effectiveAt, deletionAt }`
### POST /api/subscription/reactivate
- **Auth:** Session
- **Response:** `{ success: true }`
### GET /api/subscription/status
- **Auth:** Session
- **Response:** `{ plan, status, cancelledAt, effectiveAt, stripeSubscriptionId }`
---
## Assistants
### GET /api/assistants
- **Auth:** Session
- **Response:** `{ assistants: [{ id, vapiAssistantId, name, status, callCount }], phoneNumber }`
### POST /api/assistants
- **Auth:** Session
- **Body:** `{ vapiAssistantId, name }`
- **Response:** `{ assistant }` (201)
### PATCH /api/assistants/[id]
- **Auth:** Session
- **Body:** `{ name }`
- **Response:** `{ assistant }`
### DELETE /api/assistants/[id]
- **Auth:** Session
- **Response:** `{ success: true }`
### GET /api/assistant-name
- **Auth:** Session
- **Response:** `{ name, defaultName, canEdit }` (canEdit: Pro+)
### PUT /api/assistant-name
- **Auth:** Session | **Plan:** Pro+
- **Body:** `{ name }` (2-30 chars)
- **Response:** `{ success: true }`
### GET /api/first-message-mode
- **Auth:** Session
- **Response:** `{ mode, firstMessage }`
### PUT /api/first-message-mode
- **Auth:** Session
- **Body:** `{ mode, firstMessage? }`
- **Response:** `{ success: true }`
- **Modes:** assistant-speaks-first, assistant-waits-for-user, assistant-speaks-first-with-model-generated-message
---
## Voice & Prompt
### GET /api/voice
- **Auth:** Session
- **Response:** `{ provider, voiceId, voiceName, canEdit }` (canEdit: Advanced+)
### PUT /api/voice
- **Auth:** Session | **Plan:** Advanced+
- **Body:** `{ voiceId, voiceName? }`
- **Response:** `{ success: true }`
### GET /api/voice/catalog
- **Auth:** None
- **Response:** `[{ id, name, gender, desc, previewUrl }]`
- **Notes:** Cached 24 hours
### GET /api/prompt
- **Auth:** Session
- **Response:** `{ systemPrompt, assistantName, vapiAssistantId, canEdit }` (canEdit: Advanced+)
### PUT /api/prompt
- **Auth:** Session | **Plan:** Advanced+
- **Body:** `{ systemPrompt }`
- **Response:** `{ success: true }`
### POST /api/prompt/regenerate
- **Auth:** Session | **Plan:** Advanced+
- **Body:** `{ sections?: ["persona", "context"] }`
- **Response:** `{ success, systemPrompt, regenerated }`
---
## Knowledge Base
### GET /api/kb
- **Auth:** Session
- **Response:** `{ assistantId, vapiAssistantId, canEdit, sections: [{ key, label, text, vapiFileId, updatedAt }] }` (canEdit: Starter+)
### PUT /api/kb
- **Auth:** Session | **Plan:** Starter+
- **Body:** `{ sectionKey, text?, vapiFileId?, fileName? }`
- **Response:** `{ success, section: { key, vapiFileId, fileName } }`
### DELETE /api/kb?section=faqs
- **Auth:** Session | **Plan:** Starter+
- **Response:** `{ success: true }`
### POST /api/kb/upload
- **Auth:** Session
- **Body:** multipart/form-data: `file` + `section`
- **Response:** `{ fileId, fileName, fileSize }`
- **Notes:** Max 10MB. Types: PDF, DOCX, CSV, TXT.
---
## Calls
### GET /api/calls/[id]
- **Auth:** Session
- **Response:** `{ call: { id, vapiCallId, status, startedAt, endedAt, durationMinutes, customerNumber, transcript, summary }, plan, features }`
- **Notes:** Transcripts/recordings gated by plan.
### GET /api/calls/browser-token
- **Auth:** Session | **Plan:** Enterprise
- **Response:** `{ token, identity, phoneNumber, expiresIn: 3600 }`
### POST /api/calls/outbound
- **Auth:** Session | **Plan:** Enterprise
- **Body:** `{ customerNumber }`
- **Response:** `{ success, call: { id, status, customerNumber } }`
### GET /api/calls/outbound/status?vapiCallId=...
- **Auth:** Session
- **Response:** `{ id, status, startedAt, endedAt, minutes, endReason }`
---
## SMS
### POST /api/sms/send
- **Auth:** Session | **Plan:** Pro+
- **Body:** `{ to, body }`
- **Response:** `{ success, messageSid, status }`
- **Notes:** Requires A2P CAMPAIGN_APPROVED status.
### GET /api/sms/logs?page=1&limit=20&direction=INBOUND
- **Auth:** Session
- **Response:** `{ logs: [...], pagination: { page, limit, total, totalPages } }`
---
## Phone Numbers
### GET /api/phone-numbers
- **Auth:** Session
- **Response:** `{ phoneNumber, vapiPhoneNumberId, twilioPhoneNumberSid, hasSipTrunk, hasAssistant, assistantName }`
### POST /api/phone-numbers
- **Auth:** Session
- **Body:** `{ areaCode? }`
- **Response:** `{ success, phoneNumber, vapiPhoneNumberId, twilioPhoneNumberSid }`
### GET /api/phone-numbers/search?areaCode=415&limit=5
- **Auth:** Session
- **Response:** `{ numbers: [{ phoneNumber, locality, region }] }`
### GET /api/transfer-number
- **Auth:** Session
- **Response:** `{ transferNumber, canEdit }` (canEdit: Advanced+)
### PUT /api/transfer-number
- **Auth:** Session | **Plan:** Advanced+
- **Body:** `{ transferNumber? }`
- **Response:** `{ success, transferNumber }`
---
## Business Settings
### GET /api/business-info
- **Auth:** Session
- **Response:** `{ businessType, businessDescription, websiteUrl }`
### PUT /api/business-info
- **Auth:** Session
- **Body:** `{ businessType?, businessDescription?, websiteUrl? }`
- **Response:** `{ success, businessType, businessDescription, websiteUrl }`
### GET /api/business-hours
- **Auth:** Session
- **Response:** `{ businessHours, timezone }`
### PUT /api/business-hours
- **Auth:** Session
- **Body:** `{ businessHours, timezone? }`
- **Response:** `{ success, businessHours, timezone }`
### GET /api/business-address
- **Auth:** Session
- **Response:** `{ businessAddress }`
### PUT /api/business-address
- **Auth:** Session
- **Body:** `{ businessAddress }`
- **Response:** `{ success, businessAddress }`
---
## Calendar Integration
### POST /api/calendar/connect
- **Auth:** Session | **Plan:** Advanced+
- **Body:** `{ provider: "CAL_COM", apiKey, eventTypeId }`
- **Response:** `{ success, provider }` (201)
### GET /api/calendar/status
- **Auth:** Session
- **Response:** `{ connected, provider, config, connectedAt, canEdit }`
### POST /api/calendar/disconnect
- **Auth:** Session
- **Response:** `{ success: true }`
### GET /api/calendar/test
- **Auth:** Session
- **Response:** `{ ok, timestamp, steps: [{ name, ok }] }`
---
## Widget & Embed Tokens
### POST /api/widget/token
- **Auth:** Embed Token
- **Body:** `{ embedToken: "ept_..." }`
- **Response:** `{ token, identity, phoneNumber, orgName, expiresIn: 3600 }`
- **Notes:** CORS-enabled. Domain whitelist enforced. 1-hour TTL.
### GET /api/embed-token
- **Auth:** Session | **Plan:** Pro+
- **Response:** `{ tokens: [{ id, label, tokenPrefix, allowedDomains, lastUsedAt, createdAt }] }`
### POST /api/embed-token
- **Auth:** Session | **Plan:** Pro+
- **Body:** `{ label?, allowedDomains?: string[] }`
- **Response:** `{ token: "ept_...", prefix, label }`
- **Notes:** Token shown only once.
### DELETE /api/embed-token?id=...
- **Auth:** Session
- **Response:** `{ revoked: true }`
---
## A2P Compliance (SMS Registration)
### GET /api/a2p/status
- **Auth:** Session
- **Response:** `{ status, paid, brandType, messagingServiceSid, smsUsed, hasSmsSubscription }`
### POST /api/a2p/initiate
- **Auth:** Session | **Plan:** Pro+
- **Response:** `{ success, status: "BRAND_PENDING" }`
### POST /api/a2p/checkout
- **Auth:** Session | **Plan:** Pro+
- **Body:** `{ businessInfo, representativeInfo, campaignInfo }`
- **Response:** `{ url }`
### POST /api/a2p/campaign
- **Auth:** Session | **Plan:** Pro+
- **Body:** `{ campaignInfo }`
- **Response:** `{ success, campaignSid, status: "CAMPAIGN_PENDING" }`
---
## Onboarding
### GET /api/onboarding
- **Auth:** Session
- **Response:** `{ completed, completedAt, data, phoneNumber, organizationName }`
### POST /api/onboarding
- **Auth:** Session
- **Body:** `{ onboardingData, complete? }`
- **Response:** `{ success, completed, phoneNumber?, provisioningErrors? }`
### POST /api/onboarding/analyze
- **Auth:** Session
- **Body:** `{ url }`
- **Response:** `{ extractedData, businessName, pagesScraped, scrapeWarnings }`
---
## Webhooks (Incoming)
### POST /api/webhooks/vapi
- VAPI call events (call.started, call.ended, end-of-call-report, tool-calls). Always returns 200.
### POST /api/webhooks/stripe
- Stripe payment events. Verified via webhook signature.
### POST /api/webhooks/twilio/voice
- TwiML generation for voice calls.
### POST /api/webhooks/twilio/status
- Call status updates (ringing, in-progress, completed).
### POST /api/webhooks/twilio/sms-inbound
- Inbound SMS messages.
### POST /api/webhooks/twilio/sms-status
- SMS delivery status updates.
---
## Cron Jobs
### GET /api/cron/billing-reset
- **Auth:** Cron | Daily 00:00 UTC | Resets monthly minute counters
### GET /api/cron/sync-calls
- **Auth:** Cron | Daily 00:00 UTC | Syncs call data from VAPI
### GET /api/cron/overage-billing
- **Auth:** Cron | Weekly Monday 11:00 UTC | Bills overage minutes
### GET /api/cron/payment-retry
- **Auth:** Cron | Daily 12:00 UTC | Retries failed payments
### GET /api/cron/a2p-status
- **Auth:** Cron | Daily 00:00 UTC | Checks A2P registration status