DevelopersAPI referencePhone

Phone API

REST endpoints for phone in ServFlo: 11 operations with parameters, request and response fields, permissions and curl examples.

Base URL: https://app.servflo.ai/api. Every call needs Authorization: Bearer sfk_… (Authentication); {organizationId} ($ORGANIZATION_ID in the examples) is your business's id.

OperationEndpointRisk
Phone settingsGET /organizations/{organizationId}/phone/settingsread
Change phone settingsPATCH /organizations/{organizationId}/phone/settingswrite
Connect voicePOST /organizations/{organizationId}/phone/connectsensitive
Browser phone tokenPOST /organizations/{organizationId}/phone/softphone-tokenwrite
Calls ringing for meGET /organizations/{organizationId}/phone/liveread
Simulate an inbound call (fake mode)POST /organizations/{organizationId}/phone/simulatewrite
AI receptionist settingsGET /organizations/{organizationId}/phone/ai-receptionistread
Change the AI receptionistPATCH /organizations/{organizationId}/phone/ai-receptionistwrite
Hear a voice for the AI receptionistGET /organizations/{organizationId}/phone/ai-receptionist/voice-sampleread
Simulate a call to the AI receptionist (fake mode)POST /organizations/{organizationId}/phone/ai-receptionist/simulatewrite
Run phone work nowPOST /organizations/{organizationId}/phone/runwrite

Phone settings

GET /organizations/{organizationId}/phone/settings

The phone system's settings: on/off, the mode (twilio, or fake = simulated calls), business hours in the business's timezone, after-hours voicemail or forwarding, ring timeout and the default ring group, voicemail greeting, call recording with its consent announcement and retention, transcription and AI summaries, missed-call follow-ups and text-back, and the webhook URLs Twilio calls. Read-only.

Permissionphone.manage (Owner, Admin)
Riskread, OAuth scope servflo.read. Read: reads only.
MCP toolphone_settings (toolset marketing)

Example

curl "https://app.servflo.ai/api/organizations/$ORGANIZATION_ID/phone/settings" \
  -H "Authorization: Bearer $SERVFLO_API_KEY"

Response

200: the answer's fields
FieldTypeDescription
modeenumfake = simulated calls (development, tests). One of fake, twilio, off.
enabledboolean
twilioSourceenumManaged: the business's ServFlo Twilio sub-account (billed at Twilio's cost + 20%); OWN: its own Twilio account connected in Settings → Messaging. One of MANAGED, OWN.
managedAccountobject or nullThe ServFlo sub-account (MANAGED, once made).
managedAccount.accountSidHintstring
managedAccount.statusenumOne of ACTIVE, SUSPENDED, CLOSED.
managedAccount.fakeboolean
managedAccount.suspendReasonstring or null
managedAccount.closeAfterstring (date-time) or null
accountConnectedbooleanThe Twilio account calls run on is ready: the ServFlo sub-account (active), or the business's own account connected for texts.
browserCallingbooleanThe browser phone's API key and TwiML App exist.
businessHoursobject
afterHoursActionenumOne of VOICEMAIL, FORWARD.
afterHoursForwardNumberstring or null
ringTimeoutSecondsinteger
defaultRingGroupIdstring or null
voicemailGreetingstring or null
recordCallsboolean
announceRecordingboolean
recordingAnnouncementstring or null
businessStatestring or null
recordingRetentionDaysinteger
transcribeCallsboolean
aiSummariesboolean
missedCallFollowUpboolean
missedCallTextBackboolean
missedCallTextBodystring or null
webhookUrlsobject or nullWhere Twilio sends calls (set on bought or assigned numbers automatically).
webhookUrls.voicestring
webhookUrls.statusstring
webhookUrls.browserstring
timeZonestring
updatedAtstring (date-time)

Change phone settings

PATCH /organizations/{organizationId}/phone/settings

Change how calls are handled from the next call on: business hours, after hours, ring timeout, default ring group, voicemail greeting, recording (announced by default; two-party-consent states are always announced), retention, transcription, AI summaries (metered against AI credits), missed-call follow-ups and the missed-call text (sent only to known customers whose number is OK to text). enabled: false stops answering calls.

Permissionphone.manage (Owner, Admin)
Riskwrite, OAuth scope servflo.write. Write: creates or changes records inside the business; nothing leaves it.
MCP toolphone_updateSettings (toolset marketing)
PlanNeeds the Phone system add-on.

Request body

FieldTypeRequiredDescription
enabledbooleanNo
businessHoursobjectNoOpening hours per weekday, e.g. { "mon": [{ "start": "08:00", "end": "17:00" }] }.
businessHours.sunarray of objectNoUp to 4 items.
businessHours.sun[].startstringYesHh: MM, 24-hour, in the business's timezone.
businessHours.sun[].endstringYesHh: MM, 24-hour, in the business's timezone.
businessHours.monarray of objectNoUp to 4 items.
businessHours.mon[].startstringYesHh: MM, 24-hour, in the business's timezone.
businessHours.mon[].endstringYesHh: MM, 24-hour, in the business's timezone.
businessHours.tuearray of objectNoUp to 4 items.
businessHours.tue[].startstringYesHh: MM, 24-hour, in the business's timezone.
businessHours.tue[].endstringYesHh: MM, 24-hour, in the business's timezone.
businessHours.wedarray of objectNoUp to 4 items.
businessHours.wed[].startstringYesHh: MM, 24-hour, in the business's timezone.
businessHours.wed[].endstringYesHh: MM, 24-hour, in the business's timezone.
businessHours.thuarray of objectNoUp to 4 items.
businessHours.thu[].startstringYesHh: MM, 24-hour, in the business's timezone.
businessHours.thu[].endstringYesHh: MM, 24-hour, in the business's timezone.
businessHours.friarray of objectNoUp to 4 items.
businessHours.fri[].startstringYesHh: MM, 24-hour, in the business's timezone.
businessHours.fri[].endstringYesHh: MM, 24-hour, in the business's timezone.
businessHours.satarray of objectNoUp to 4 items.
businessHours.sat[].startstringYesHh: MM, 24-hour, in the business's timezone.
businessHours.sat[].endstringYesHh: MM, 24-hour, in the business's timezone.
afterHoursActionenumNoOne of VOICEMAIL, FORWARD.
afterHoursForwardNumberstring or nullNoA phone number; +E.164 is safest (+13125550100). 10 digits are read as US. Up to 32 characters.
ringTimeoutSecondsintegerNo5–60.
defaultRingGroupIdstring or nullNoRings when a number has no group of its own.
voicemailGreetingstring or nullNoUp to 500 characters.
recordCallsbooleanNo
announceRecordingbooleanNoAnnounce recording (default on). Off is honoured only when both parties are known to be in one-party-consent states.
recordingAnnouncementstring or nullNoUp to 500 characters.
businessStatestring or nullNoThe business's US state for the recording consent rule.
recordingRetentionDaysintegerNo1–3650.
transcribeCallsbooleanNo
aiSummariesbooleanNo
missedCallFollowUpbooleanNo
missedCallTextBackbooleanNoText a known customer back after a missed call (needs OK to text).
missedCallTextBodystring or nullNoUp to 320 characters.

Example

curl -X PATCH "https://app.servflo.ai/api/organizations/$ORGANIZATION_ID/phone/settings" \
  -H "Authorization: Bearer $SERVFLO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"enabled":true,"businessHours":{"sun":[{"start":"09:00","end":"09:00"}],"mon":[{"start":"09:00","end":"09:00"}],"tue":[{"start":"09:00","end":"09:00"}]},"afterHoursAction":"VOICEMAIL"}'

Response

200: the answer's fields
FieldTypeDescription
modeenumfake = simulated calls (development, tests). One of fake, twilio, off.
enabledboolean
twilioSourceenumManaged: the business's ServFlo Twilio sub-account (billed at Twilio's cost + 20%); OWN: its own Twilio account connected in Settings → Messaging. One of MANAGED, OWN.
managedAccountobject or nullThe ServFlo sub-account (MANAGED, once made).
managedAccount.accountSidHintstring
managedAccount.statusenumOne of ACTIVE, SUSPENDED, CLOSED.
managedAccount.fakeboolean
managedAccount.suspendReasonstring or null
managedAccount.closeAfterstring (date-time) or null
accountConnectedbooleanThe Twilio account calls run on is ready: the ServFlo sub-account (active), or the business's own account connected for texts.
browserCallingbooleanThe browser phone's API key and TwiML App exist.
businessHoursobject
afterHoursActionenumOne of VOICEMAIL, FORWARD.
afterHoursForwardNumberstring or null
ringTimeoutSecondsinteger
defaultRingGroupIdstring or null
voicemailGreetingstring or null
recordCallsboolean
announceRecordingboolean
recordingAnnouncementstring or null
businessStatestring or null
recordingRetentionDaysinteger
transcribeCallsboolean
aiSummariesboolean
missedCallFollowUpboolean
missedCallTextBackboolean
missedCallTextBodystring or null
webhookUrlsobject or nullWhere Twilio sends calls (set on bought or assigned numbers automatically).
webhookUrls.voicestring
webhookUrls.statusstring
webhookUrls.browserstring
timeZonestring
updatedAtstring (date-time)

Connect voice

POST /organizations/{organizationId}/phone/connect

Turn the phone system on. Makes the business's ServFlo Twilio sub-account the first time (MANAGED, the default; usage billed at Twilio's cost + 20%), then creates an API key and a TwiML App in that account (or in the business's own Twilio account when it chose OWN) for the browser phone; safe to repeat (the App's URLs are refreshed). In fake mode it records simulated ids. Call GET /phone/settings first to see the mode.

Permissionphone.manage (Owner, Admin)
Risksensitive, OAuth scope servflo.sensitive. Sensitive: moves money, deletes, sends or shows something to a customer, or grants or revokes access.
MCP toolphone_connect (toolset marketing)
PlanNeeds the Phone system add-on.
ApprovalOver MCP, this tool asks for your OK first (in the AI app when it supports it, otherwise in ServFlo). See Approvals.

Example

curl -X POST "https://app.servflo.ai/api/organizations/$ORGANIZATION_ID/phone/connect" \
  -H "Authorization: Bearer $SERVFLO_API_KEY"

Response

200: the answer's fields
FieldTypeDescription
modeenumfake = simulated calls (development, tests). One of fake, twilio, off.
enabledboolean
twilioSourceenumManaged: the business's ServFlo Twilio sub-account (billed at Twilio's cost + 20%); OWN: its own Twilio account connected in Settings → Messaging. One of MANAGED, OWN.
managedAccountobject or nullThe ServFlo sub-account (MANAGED, once made).
managedAccount.accountSidHintstring
managedAccount.statusenumOne of ACTIVE, SUSPENDED, CLOSED.
managedAccount.fakeboolean
managedAccount.suspendReasonstring or null
managedAccount.closeAfterstring (date-time) or null
accountConnectedbooleanThe Twilio account calls run on is ready: the ServFlo sub-account (active), or the business's own account connected for texts.
browserCallingbooleanThe browser phone's API key and TwiML App exist.
businessHoursobject
afterHoursActionenumOne of VOICEMAIL, FORWARD.
afterHoursForwardNumberstring or null
ringTimeoutSecondsinteger
defaultRingGroupIdstring or null
voicemailGreetingstring or null
recordCallsboolean
announceRecordingboolean
recordingAnnouncementstring or null
businessStatestring or null
recordingRetentionDaysinteger
transcribeCallsboolean
aiSummariesboolean
missedCallFollowUpboolean
missedCallTextBackboolean
missedCallTextBodystring or null
webhookUrlsobject or nullWhere Twilio sends calls (set on bought or assigned numbers automatically).
webhookUrls.voicestring
webhookUrls.statusstring
webhookUrls.browserstring
timeZonestring
updatedAtstring (date-time)

Browser phone token

POST /organizations/{organizationId}/phone/softphone-token

What the browser phone needs for the signed-in person: mode TWILIO with a one-hour Twilio Voice access token (identity derived from the user; receives the ring group's calls, places calls through the business number), FAKE (simulated calls), OFF (phone system off) or UNAVAILABLE (connect voice first). Nothing else changes.

Permission: phone.calls (Owner, Admin, Dispatcher, Technician (own jobs)); risk write (OAuth scope servflo.write).

Permissionphone.calls
Riskwrite, OAuth scope servflo.write. Write: creates or changes records inside the business; nothing leaves it.
MCP toolphone_softphone (toolset marketing)
PlanNeeds the Phone system add-on.

Request body

FieldTypeRequiredDescription
platformenum or nullNoThe ServFlo app: a token whose calls also wake the app through push (when ServFlo's push keys are set). One of ios, android.

Example

curl -X POST "https://app.servflo.ai/api/organizations/$ORGANIZATION_ID/phone/softphone-token" \
  -H "Authorization: Bearer $SERVFLO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"platform":"ios"}'

Response

200: the answer's fields
FieldTypeDescription
modeenumOne of TWILIO, FAKE, OFF, UNAVAILABLE.
identitystring
tokenstring or null
expiresAtstring (date-time) or null
pushbooleanCalls can wake the app (a push credential is in the token).

Calls ringing for me

GET /organizations/{organizationId}/phone/live

Calls ringing for the signed-in person's ring groups and calls they are on: what the browser phone and the screen pop show. Poll every few seconds. Read-only (simulated calls nobody answered are ended as Twilio would).

Permission: phone.calls (Owner, Admin, Dispatcher, Technician (own jobs)); risk read (OAuth scope servflo.read).

Permissionphone.calls
Riskread, OAuth scope servflo.read. Read: reads only.
MCP toolphone_live (toolset marketing)

Example

curl "https://app.servflo.ai/api/organizations/$ORGANIZATION_ID/phone/live" \
  -H "Authorization: Bearer $SERVFLO_API_KEY"

Response

200: the answer's fields
FieldTypeDescription
itemsarray of object
items[].idstring
items[].directionenumOne of INBOUND, OUTBOUND.
items[].statusenumOne of RINGING, IN_PROGRESS, COMPLETED, MISSED, VOICEMAIL, FAILED.
items[].fakebooleanA simulated call (SERVFLO_VOICE=fake).
items[].fromNumberstring
items[].toNumberstring
items[].otherNumberstringThe customer's side of the call.
items[].phoneNumberobject or null
items[].phoneNumber.idstring
items[].phoneNumber.labelstring
items[].phoneNumber.e164string
items[].sourceenum or nullThe tracked source of the number called (PERSONAL: someone's own line); null = untracked. One of MAIN, WEBSITE, GOOGLE_BUSINESS, CAMPAIGN, QR_CODE, BUSINESS_UNIT, PRINT, OTHER, PERSONAL.
items[].campaignobject or null
items[].campaign.idstring
items[].campaign.namestring
items[].businessUnitobject or null
items[].businessUnit.idstring
items[].businessUnit.namestring
items[].customerobject or null
items[].customer.idstring
items[].customer.namestring
items[].matchedCustomerCountinteger
items[].userobject or nullWho answered (inbound) or placed it (outbound).
items[].user.idstring
items[].user.namestring
items[].bridgeNumberstring or null
items[].ivrDigitstring or null
items[].afterHoursboolean
items[].startedAtstring (date-time)
items[].answeredAtstring (date-time) or null
items[].endedAtstring (date-time) or null
items[].durationSecondsinteger or nullTalk time.
items[].billedMinutesinteger or null
items[].voicemailboolean
items[].recordingobject
items[].recording.statusenumOne of NONE, PENDING, DONE, FAILED, SKIPPED.
items[].recording.announcedboolean
items[].recording.durationSecondsinteger or null
items[].recording.availableboolean
items[].recording.deletedAtstring (date-time) or null
items[].recording.errorstring or null
items[].transcriptStatusenumOne of NONE, PENDING, DONE, FAILED, SKIPPED.
items[].transcriptstring or nullOnly for callers holding phone.recordings, on GET /phone/calls/{callId}.
items[].summaryStatusenumOne of NONE, PENDING, DONE, FAILED, SKIPPED.
items[].summarystring or nullThe AI call summary (Claude, from the transcript).
items[].summaryErrorstring or null
items[].followUpobject
items[].followUp.statusenumOne of NONE, OPEN, DONE.
items[].followUp.assigneeobject or null
items[].followUp.doneAtstring (date-time) or null
items[].textBackobject or null
items[].textBack.statusstring
items[].textBack.messageIdstring or null
items[].jobobject or null
items[].job.idstring
items[].job.identifierstring
items[].requestobject or null
items[].request.idstring
items[].request.identifierstring
items[].notesstring or null
items[].aiobject or nullSet when the call was routed to the AI receptionist.
items[].ai.startedAtstring (date-time) or null
items[].ai.endedAtstring (date-time) or null
items[].ai.outcomestring or nullWhat the AI receptionist did: BOOKED, RESCHEDULED, REQUEST, MESSAGE, TRANSFERRED, ENDED, ABANDONED, FAILED, TIMEOUT, DRAINED; null while live.
items[].ai.callerTurnsinteger
items[].ai.voiceSecondsinteger or nullAI voice seconds (metered as ai_voice.minutes).
items[].ai.messagestring or nullA message it took for the office.
items[].ai.turnsarray of object or nullThe conversation; only for callers holding phone.recordings, on GET /phone/calls/{callId}.
items[].createdAtstring (date-time)
items[].updatedAtstring (date-time)

Simulate an inbound call (fake mode)

POST /organizations/{organizationId}/phone/simulate

Only with simulated voice (SERVFLO_VOICE=fake; 409 otherwise): an inbound call from from to one of the business's numbers, through the same steps as Twilio's webhooks (routing, caller match, menu, recording, transcript, AI summary, follow-up, text-back). RING leaves it ringing for the browser phone; ANSWERED, MISSED and VOICEMAIL play it through. now is test-only (refused in production).

Permissionphone.manage (Owner, Admin)
Riskwrite, OAuth scope servflo.write. Write: creates or changes records inside the business; nothing leaves it.
MCP toolphone_simulate (toolset marketing)
PlanNeeds the Phone system add-on.

Request body

FieldTypeRequiredDescription
fromstringYesA phone number; +E.164 is safest (+13125550100). 10 digits are read as US. Up to 32 characters.
phoneNumberIdstring or nullNoThe number called; default the main line.
digitstring or nullNo
outcomeenumNoOne of RING, ANSWERED, MISSED, VOICEMAIL. Default RING.
answeredByUserIdstring or nullNo
talkSecondsintegerNo1–7200.
transcriptstring or nullNo
callerStatestring or nullNo
nowstring (date-time)No

Example

curl -X POST "https://app.servflo.ai/api/organizations/$ORGANIZATION_ID/phone/simulate" \
  -H "Authorization: Bearer $SERVFLO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"from":"2026-11-03"}'

Response

200: the answer's fields
FieldTypeDescription
idstring
directionenumOne of INBOUND, OUTBOUND.
statusenumOne of RINGING, IN_PROGRESS, COMPLETED, MISSED, VOICEMAIL, FAILED.
fakebooleanA simulated call (SERVFLO_VOICE=fake).
fromNumberstring
toNumberstring
otherNumberstringThe customer's side of the call.
phoneNumberobject or null
phoneNumber.idstring
phoneNumber.labelstring
phoneNumber.e164string
sourceenum or nullThe tracked source of the number called (PERSONAL: someone's own line); null = untracked. One of MAIN, WEBSITE, GOOGLE_BUSINESS, CAMPAIGN, QR_CODE, BUSINESS_UNIT, PRINT, OTHER, PERSONAL.
campaignobject or null
campaign.idstring
campaign.namestring
businessUnitobject or null
businessUnit.idstring
businessUnit.namestring
customerobject or null
customer.idstring
customer.namestring
matchedCustomerCountinteger
userobject or nullWho answered (inbound) or placed it (outbound).
user.idstring
user.namestring
bridgeNumberstring or null
ivrDigitstring or null
afterHoursboolean
startedAtstring (date-time)
answeredAtstring (date-time) or null
endedAtstring (date-time) or null
durationSecondsinteger or nullTalk time.
billedMinutesinteger or null
voicemailboolean
recordingobject
recording.statusenumOne of NONE, PENDING, DONE, FAILED, SKIPPED.
recording.announcedboolean
recording.durationSecondsinteger or null
recording.availableboolean
recording.deletedAtstring (date-time) or null
recording.errorstring or null
transcriptStatusenumOne of NONE, PENDING, DONE, FAILED, SKIPPED.
transcriptstring or nullOnly for callers holding phone.recordings, on GET /phone/calls/{callId}.
summaryStatusenumOne of NONE, PENDING, DONE, FAILED, SKIPPED.
summarystring or nullThe AI call summary (Claude, from the transcript).
summaryErrorstring or null
followUpobject
followUp.statusenumOne of NONE, OPEN, DONE.
followUp.assigneeobject or null
followUp.assignee.idstring
followUp.assignee.namestring
followUp.doneAtstring (date-time) or null
textBackobject or null
textBack.statusstring
textBack.messageIdstring or null
jobobject or null
job.idstring
job.identifierstring
requestobject or null
request.idstring
request.identifierstring
notesstring or null
aiobject or nullSet when the call was routed to the AI receptionist.
ai.startedAtstring (date-time) or null
ai.endedAtstring (date-time) or null
ai.outcomestring or nullWhat the AI receptionist did: BOOKED, RESCHEDULED, REQUEST, MESSAGE, TRANSFERRED, ENDED, ABANDONED, FAILED, TIMEOUT, DRAINED; null while live.
ai.callerTurnsinteger
ai.voiceSecondsinteger or nullAI voice seconds (metered as ai_voice.minutes).
ai.messagestring or nullA message it took for the office.
ai.turnsarray of object or nullThe conversation; only for callers holding phone.recordings, on GET /phone/calls/{callId}.
ai.turns[].roleenumOne of caller, assistant, tool.
ai.turns[].textstring
ai.turns[].atstring
ai.turns[].toolstring
createdAtstring (date-time)
updatedAtstring (date-time)

AI receptionist settings

GET /organizations/{organizationId}/phone/ai-receptionist

The AI receptionist's settings: on/off, when it answers (ALWAYS, AFTER_HOURS, OVERFLOW instead of voicemail), greeting and instructions (whether the greeting introduces it as the virtual assistant, and how it names a known caller: callerGreeting), the trade template, voice and language, who a transfer rings, and what it may do (book real arrival windows and move a known caller's visit, take requests and cancellation requests, say pricebook prices by priceMode, tell a known caller their balance, text links). Also the trade templates, the greeting as spoken, and whether this server can run it (keys, booking mode, problems to fix). Read-only.

Permissionphone.manage (Owner, Admin)
Riskread, OAuth scope servflo.read. Read: reads only.
MCP toolphone_receptionist (toolset marketing)

Example

curl "https://app.servflo.ai/api/organizations/$ORGANIZATION_ID/phone/ai-receptionist" \
  -H "Authorization: Bearer $SERVFLO_API_KEY"

Response

200: the answer's fields
FieldTypeDescription
enabledboolean
modeenumOne of ALWAYS, AFTER_HOURS, OVERFLOW.
greetingstring or null
instructionsstring or null
templatestring
voiceProviderenumOne of DEEPGRAM, ELEVENLABS, OPENAI.
voiceIdstring or null
languageenumOne of en, es.
transferRingGroupIdstring or null
transferNumberstring or null
canBookboolean
canTakeRequestsbooleanTakes service requests, and a known caller's cancellation requests (never cancels).
priceModeenumOff: never a price. STARTING_AT: pricebook prices as 'starting at', the technician confirms. EXACT: the pricebook price. One of OFF, STARTING_AT, EXACT.
quotePriceRangesbooleanDeprecated: true when priceMode is not OFF.
canTextLinksboolean
canShareBalancebooleanTells a known caller their open balance after their name matches.
announceAssistantbooleanThe greeting says "I'm the virtual assistant"; the transcription notice is always said.
callerGreetingenumHow the greeting names a caller whose number matches a customer: FIRST_NAME ("Hi Sarah"), TITLE_LAST_NAME ("Hello Mrs. Johnson"; the customer's own title, else the first name) or GENERIC (no name). Unknown numbers always get the generic greeting; the caller is still verified before their visits or account. One of FIRST_NAME, TITLE_LAST_NAME, GENERIC.
templatesarray of object
templates[].keystring
templates[].namestring
templates[].greetingstring
suggestedTemplatestring or nullThe website's trade, when a template matches it.
greetingPreviewstringThe greeting as spoken, with the AI disclosure.
readinessobject
readiness.modeenumfake = scripted speech and a scripted model (tests). One of fake, live, off.
readiness.readybooleanIt can answer calls on this server with these settings.
readiness.keysobject
readiness.keys.deepgramboolean
readiness.keys.elevenlabsboolean
readiness.keys.openaiboolean
readiness.keys.anthropicboolean
readiness.bookingModeenumBooking real windows needs online booking in SLOTS mode; otherwise it takes requests. One of SLOTS, REQUEST.
readiness.problemsarray of string
streamUrlstringWhere Twilio Media Streams connect (wss).
updatedAtstring (date-time)

Change the AI receptionist

PATCH /organizations/{organizationId}/phone/ai-receptionist

Change the AI receptionist from the next call on. When on, Claude answers calls by voice in the chosen mode, always says the call is transcribed, says it is the virtual assistant in the greeting unless announceAssistant is false, names a caller whose number matches a customer by callerGreeting (FIRST_NAME, TITLE_LAST_NAME with the customer's own title, or GENERIC; unknown numbers are always greeted generically, and a known number is still verified by full name and service ZIP before their visits or account), and never claims to be a person (asked, it says it is the business's AI assistant). It may book arrival windows and move a known caller's visit to one (canBook; the online booking rules, confirmed with the caller first), take requests, messages and a known caller's cancellation request (canTakeRequests; it never cancels: the office confirms, with a follow-up on the call and a notification), say pricebook prices (priceMode OFF, STARTING_AT or EXACT; never a cost), tell a known caller their open balance once their name matches (canShareBalance), text known customers a link (canTextLinks; consent applies), and transfer to a ring group or number. It never invents prices or availability. Its model use is metered against AI credits and its voice minutes as ai_voice.minutes. transferRingGroupId must be one of the business's ring groups. Needs the phone add-on, except to switch it off.

Permissionphone.manage (Owner, Admin)
Riskwrite, OAuth scope servflo.write. Write: creates or changes records inside the business; nothing leaves it.
MCP toolphone_updateReceptionist (toolset marketing)
PlanNeeds the AI receptionist add-on.

Request body

FieldTypeRequiredDescription
enabledbooleanNoAnswer calls with the AI receptionist.
modeenumNoAlways: it answers every call. AFTER_HOURS: it answers outside business hours (instead of the after-hours voicemail or forward). OVERFLOW: it answers when a call would go to voicemail (no one answered, no one to ring, after-hours voicemail). One of ALWAYS, AFTER_HOURS, OVERFLOW.
greetingstring or nullNoSpoken first; blank = the trade template's greeting. Up to 400 characters.
instructionsstring or nullNoWhat the business wants it to know (policies, areas, what to mention). Never put prices here unless you want them said. Up to 4000 characters.
templatestringNoA trade template key (GET /phone/ai-receptionist lists them). Up to 40 characters.
voiceProviderenumNoOne of DEEPGRAM, ELEVENLABS, OPENAI.
voiceIdstring or nullNoThe provider's voice id; blank = its default for the language. Up to 100 characters.
languageenumNoen (English) or es (Spanish). One of en, es.
transferRingGroupIdstring or nullNoWho a transfer rings (GET /phone/ring-groups); else transferNumber, else the default group.
transferNumberstring or nullNoA +E.164 number a transfer dials when no ring group is set. Up to 32 characters.
canBookbooleanNoBook real arrival windows (needs online booking in SLOTS mode).
canTakeRequestsbooleanNoTake a service request (lead) for the office, and a known caller's request to cancel a visit (it never cancels: the office confirms).
priceModeenumNoPricebook prices when a caller asks: OFF (never a price; a technician confirms it), STARTING_AT ("starting at $X", the technician confirms the final price; the default), EXACT (the pricebook price). Never a cost or margin, never a price the pricebook does not hold. One of OFF, STARTING_AT, EXACT.
quotePriceRangesbooleanNoDeprecated: use priceMode. true = STARTING_AT, false = OFF; ignored when priceMode is given.
canTextLinksbooleanNoText a known caller a booking, payment or portal link (consent rules apply).
canShareBalancebooleanNoTell a known caller (caller ID matches a customer) their open balance, only after they say their name and it matches; then it offers to text the payment link (needs canTextLinks).
announceAssistantbooleanNoSay "I'm the virtual assistant" in the greeting (default on). Off: the greeting keeps only the transcription notice. Either way it never claims to be a person and says it is the business's virtual (AI) assistant when asked.
callerGreetingenumNoHow the greeting names a caller whose number matches a customer: FIRST_NAME ("Hi Sarah", the default), TITLE_LAST_NAME ("Hello Mrs. Johnson": the customer's own title (Mr., Mrs., Ms., Miss, Dr., Mx.) and last name; without both it says the first name, without a usable name nothing) or GENERIC (no name). Unknown numbers always get the generic greeting. Caller ID can be faked, so it still verifies the caller (full name and service ZIP) before talking about their visits or account. One of FIRST_NAME, TITLE_LAST_NAME, GENERIC.

Example

curl -X PATCH "https://app.servflo.ai/api/organizations/$ORGANIZATION_ID/phone/ai-receptionist" \
  -H "Authorization: Bearer $SERVFLO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"enabled":true,"mode":"ALWAYS","greeting":"string"}'

Response

200: the answer's fields
FieldTypeDescription
enabledboolean
modeenumOne of ALWAYS, AFTER_HOURS, OVERFLOW.
greetingstring or null
instructionsstring or null
templatestring
voiceProviderenumOne of DEEPGRAM, ELEVENLABS, OPENAI.
voiceIdstring or null
languageenumOne of en, es.
transferRingGroupIdstring or null
transferNumberstring or null
canBookboolean
canTakeRequestsbooleanTakes service requests, and a known caller's cancellation requests (never cancels).
priceModeenumOff: never a price. STARTING_AT: pricebook prices as 'starting at', the technician confirms. EXACT: the pricebook price. One of OFF, STARTING_AT, EXACT.
quotePriceRangesbooleanDeprecated: true when priceMode is not OFF.
canTextLinksboolean
canShareBalancebooleanTells a known caller their open balance after their name matches.
announceAssistantbooleanThe greeting says "I'm the virtual assistant"; the transcription notice is always said.
callerGreetingenumHow the greeting names a caller whose number matches a customer: FIRST_NAME ("Hi Sarah"), TITLE_LAST_NAME ("Hello Mrs. Johnson"; the customer's own title, else the first name) or GENERIC (no name). Unknown numbers always get the generic greeting; the caller is still verified before their visits or account. One of FIRST_NAME, TITLE_LAST_NAME, GENERIC.
templatesarray of object
templates[].keystring
templates[].namestring
templates[].greetingstring
suggestedTemplatestring or nullThe website's trade, when a template matches it.
greetingPreviewstringThe greeting as spoken, with the AI disclosure.
readinessobject
readiness.modeenumfake = scripted speech and a scripted model (tests). One of fake, live, off.
readiness.readybooleanIt can answer calls on this server with these settings.
readiness.keysobject
readiness.keys.deepgramboolean
readiness.keys.elevenlabsboolean
readiness.keys.openaiboolean
readiness.keys.anthropicboolean
readiness.bookingModeenumBooking real windows needs online booking in SLOTS mode; otherwise it takes requests. One of SLOTS, REQUEST.
readiness.problemsarray of string
streamUrlstringWhere Twilio Media Streams connect (wss).
updatedAtstring (date-time)

Hear a voice for the AI receptionist

GET /organizations/{organizationId}/phone/ai-receptionist/voice-sample

Speaks the receptionist's opening line ("Thanks for calling <business>…") in a voice, so the owner can compare voices before choosing one. Returns a short 8 kHz WAV, base64, exactly as a caller hears it on the phone. Changes nothing (the voice is chosen with PATCH …/phone/ai-receptionist). The voices offered per provider and language are AI_VOICE_CATALOG (voiceId blank = the provider's default). MONEY: a live sample uses AI credits (about 1 per sample, for the provider's text-to-speech); 402 PLAN_LIMIT_REACHED when the business has none left. Limited per business. 409 when this server has no voice for that provider; simulated = silence (SERVFLO_VOICE_AI=fake), free.

Permissionphone.manage (Owner, Admin)
Riskread, OAuth scope servflo.read. Read: reads only.
MCP toolphone_voiceSample (toolset marketing)

Parameters

NameInTypeRequiredDescription
providerqueryenumYesOne of DEEPGRAM, ELEVENLABS, OPENAI.
voiceIdquerystringNoThe provider's voice id; blank = its default for the language. Up to 100 characters.
languagequeryenumYesOne of en, es.

Example

curl "https://app.servflo.ai/api/organizations/$ORGANIZATION_ID/phone/ai-receptionist/voice-sample?provider=DEEPGRAM&language=en" \
  -H "Authorization: Bearer $SERVFLO_API_KEY"

Response

200: the answer's fields
FieldTypeDescription
audioBase64stringThe WAV file, base64.
contentTypeaudio/wav
simulatedbooleanTrue when this server's voice is simulated: silence.

Simulate a call to the AI receptionist (fake mode)

POST /organizations/{organizationId}/phone/ai-receptionist/simulate

Only with simulated voice and SERVFLO_VOICE_AI=fake (409 otherwise): a call from from goes through routing to the receptionist, which runs the Twilio Media Streams protocol in-process with scripted speech-to-text (script: the caller's lines) and a scripted model (/tool name {json} calls a tool, bye ends the call, anything else is echoed). Writes the call log, the conversation, the request or booking it made and the summary, as a real call would. now is test-only.

Permissionphone.manage (Owner, Admin)
Riskwrite, OAuth scope servflo.write. Write: creates or changes records inside the business; nothing leaves it.
MCP toolphone_simulateReceptionist (toolset marketing)
PlanNeeds the AI receptionist add-on.

Request body

FieldTypeRequiredDescription
fromstringYesA phone number; +E.164 is safest (+13125550100). 10 digits are read as US. Up to 32 characters.
phoneNumberIdstring or nullNoThe number called; default the main line.
scriptarray of stringYesUp to 40 items.
answerTransferbooleanNoAfter a transfer, the ring group answers (default: nobody does).
nowstring (date-time)No

Example

curl -X POST "https://app.servflo.ai/api/organizations/$ORGANIZATION_ID/phone/ai-receptionist/simulate" \
  -H "Authorization: Bearer $SERVFLO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"from":"2026-11-03","script":["string"]}'

Response

200: the answer's fields
FieldTypeDescription
callobject
call.idstring
call.directionenumOne of INBOUND, OUTBOUND.
call.statusenumOne of RINGING, IN_PROGRESS, COMPLETED, MISSED, VOICEMAIL, FAILED.
call.fakebooleanA simulated call (SERVFLO_VOICE=fake).
call.fromNumberstring
call.toNumberstring
call.otherNumberstringThe customer's side of the call.
call.phoneNumberobject or null
call.phoneNumber.idstring
call.phoneNumber.labelstring
call.phoneNumber.e164string
call.sourceenum or nullThe tracked source of the number called (PERSONAL: someone's own line); null = untracked. One of MAIN, WEBSITE, GOOGLE_BUSINESS, CAMPAIGN, QR_CODE, BUSINESS_UNIT, PRINT, OTHER, PERSONAL.
call.campaignobject or null
call.campaign.idstring
call.campaign.namestring
call.businessUnitobject or null
call.businessUnit.idstring
call.businessUnit.namestring
call.customerobject or null
call.customer.idstring
call.customer.namestring
call.matchedCustomerCountinteger
call.userobject or nullWho answered (inbound) or placed it (outbound).
call.user.idstring
call.user.namestring
call.bridgeNumberstring or null
call.ivrDigitstring or null
call.afterHoursboolean
call.startedAtstring (date-time)
call.answeredAtstring (date-time) or null
call.endedAtstring (date-time) or null
call.durationSecondsinteger or nullTalk time.
call.billedMinutesinteger or null
call.voicemailboolean
call.recordingobject
call.recording.statusenumOne of NONE, PENDING, DONE, FAILED, SKIPPED.
call.recording.announcedboolean
call.recording.durationSecondsinteger or null
call.recording.availableboolean
call.recording.deletedAtstring (date-time) or null
call.recording.errorstring or null
call.transcriptStatusenumOne of NONE, PENDING, DONE, FAILED, SKIPPED.
call.transcriptstring or nullOnly for callers holding phone.recordings, on GET /phone/calls/{callId}.
call.summaryStatusenumOne of NONE, PENDING, DONE, FAILED, SKIPPED.
call.summarystring or nullThe AI call summary (Claude, from the transcript).
call.summaryErrorstring or null
call.followUpobject
call.followUp.statusenumOne of NONE, OPEN, DONE.
call.followUp.assigneeobject or null
call.followUp.doneAtstring (date-time) or null
call.textBackobject or null
call.textBack.statusstring
call.textBack.messageIdstring or null
call.jobobject or null
call.job.idstring
call.job.identifierstring
call.requestobject or null
call.request.idstring
call.request.identifierstring
call.notesstring or null
call.aiobject or nullSet when the call was routed to the AI receptionist.
call.ai.startedAtstring (date-time) or null
call.ai.endedAtstring (date-time) or null
call.ai.outcomestring or nullWhat the AI receptionist did: BOOKED, RESCHEDULED, REQUEST, MESSAGE, TRANSFERRED, ENDED, ABANDONED, FAILED, TIMEOUT, DRAINED; null while live.
call.ai.callerTurnsinteger
call.ai.voiceSecondsinteger or nullAI voice seconds (metered as ai_voice.minutes).
call.ai.messagestring or nullA message it took for the office.
call.ai.turnsarray of object or nullThe conversation; only for callers holding phone.recordings, on GET /phone/calls/{callId}.
call.createdAtstring (date-time)
call.updatedAtstring (date-time)
afterStreamenumOne of TRANSFER, VOICEMAIL, HANGUP.
framesintegerAudio frames (20 ms) the receptionist sent.
marksinteger
clearsintegerBarge-ins: the caller talked over it.

Run phone work now

POST /organizations/{organizationId}/phone/run

Run this business's pending phone work now instead of waiting for the worker: recording downloads into storage, AI summaries, simulated calls nobody answered, and recordings past retention. now is test-only (refused in production).

Permissionphone.manage (Owner, Admin)
Riskwrite, OAuth scope servflo.write. Write: creates or changes records inside the business; nothing leaves it.
MCP toolphone_run (toolset marketing)
PlanNeeds the Phone system add-on.

Request body

FieldTypeRequiredDescription
nowstring (date-time)No

Example

curl -X POST "https://app.servflo.ai/api/organizations/$ORGANIZATION_ID/phone/run" \
  -H "Authorization: Bearer $SERVFLO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"now":"2026-11-03T15:00:00Z"}'

Response

200: the answer's fields
FieldTypeDescription
processedinteger
purgedinteger

On this page