For AI agents: the documentation index is at /docs/formbox/llms.txt. A Markdown version of this page is available at /docs/formbox/reference/voice-agents-api.md or by requesting it with the Accept: text/markdown header.
Formbox Docs

Voice Agents API

How-to: Voice Agents.

$call is the integration operation. $hangup and GET /sdc/voice/run/{call-id} belong on the same path. Agents themselves are created with ordinary FHIR CRUD.

Request and response bodies for $call use FHIR Parameters. Validation errors are OperationOutcome.

Resources

SDCVoicePrompt

Versioned instruction text. Identity is url and is stable across revisions: an agent points at the lineage, and the active revision is resolved when the call is placed.

ElementCardinalityTypeDescription
url1..1uriCanonical identity of the lineage, stable across versions
version0..1stringBusiness version of this revision
name0..1stringMachine-readable name
title0..1stringHuman-readable name
status1..1codedraft | active | retired. Only active revisions are picked up
date0..1dateTimeWhen this revision was authored
publisher0..1stringWho authored this revision
description0..1markdownWhat this prompt is for, and what changed
role0..1codeinterviewer | summarizer | extractor
prompt1..1markdownThe instruction text, sent as the system message
PUT /fhir/SDCVoicePrompt/post-discharge-v1
content-type: text/yaml

resourceType: SDCVoicePrompt
url: http://example.org/SDCVoicePrompt/post-discharge
version: '1.0.0'
name: post-discharge-checkin
title: Post-discharge check-in
status: active
role: interviewer
prompt: |
  You are a nurse assistant calling a patient two days after discharge.
  Be warm and brief. Ask one question at a time and confirm what you heard
  before moving on. If the patient reports chest pain or shortness of breath,
  tell them to contact emergency services and end the call.

The hang-up instruction is appended by Aidbox to every prompt, so it does not belong in the text.

SDCVoiceAgent

Binds a prompt to the questionnaires it administers, the context it needs, and the models it speaks with.

ElementCardinalityTypeDescription
url0..1uriCanonical identity of this agent
name0..1stringMachine-readable name
title0..1stringHuman-readable name
status1..1codedraft | active | retired. Only active agents can be called
description0..1markdownWhat this agent is for
prompt1..1canonicalSDCVoicePrompt lineage url. Version-less
questionnaire1..*canonicalQuestionnaires to administer. Array order is interview order
context0..*BackboneElementResources the agent needs, validated before the call is placed
context.name1..1codeSlot name used in $call and in the prompt, e.g. patient
context.type1..1codeResource type accepted in this slot
context.label0..1stringHuman-readable name for the UI picker
llm0..1Reference(Device)Conversation model. Falls back to the deployment default
stt0..1Reference(Device)Speech-to-text model
tts0..1Reference(Device)Text-to-speech voice
extractorLlm0..1Reference(Device)Model that reads the transcript into answers. Falls back to llm
maxAttempts0..1integerRedials when nobody picks up. Defaults to a single attempt
retryDelayMinutes0..1integerGap between redial attempts
maxDurationMinutes0..1integerHard cap on one conversation; the call is hung up when reached
PUT /fhir/SDCVoiceAgent/post-discharge
content-type: text/yaml

resourceType: SDCVoiceAgent
url: http://example.org/SDCVoiceAgent/post-discharge
name: post-discharge-checkin
title: Post-discharge check-in
status: active
prompt: http://example.org/SDCVoicePrompt/post-discharge
questionnaire:
- http://example.org/Questionnaire/discharge-symptoms
- http://example.org/Questionnaire/medication-adherence
context:
- name: patient
  type: Patient
  label: Patient
- name: encounter
  type: Encounter
  label: Discharge encounter
llm:
  reference: Device/gpt-4o-mini
stt:
  reference: Device/flux-general-en
tts:
  reference: Device/sonic-3-5
extractorLlm:
  reference: Device/gpt-4o
maxAttempts: 3
retryDelayMinutes: 60
maxDurationMinutes: 15

A phone call needs one context slot of type Patient — that is who gets dialled.

Device (a model)

Each model is a Device, so a Provenance can point at the model that produced the answers. The designer creates these on first use; an integration can write them itself.

The Device, Provenance and prompt documents are shaped after the HL7 AI Transparency on FHIR IG — AI-Device, AI-Provenance, AI-InputPrompt, and the AIKind / AIconfidence extensions.

That IG is still in STU1 ballot, so its profiles and codes may change. Aidbox follows its shapes but does not claim conformance.

PUT /fhir/Device/gpt-4o-mini
content-type: text/yaml

resourceType: Device
meta:
  profile:
  - http://hl7.org/fhir/uv/aitransparency/StructureDefinition/AI-Device
status: active
identifier:
- system: urn:sdc:voice:openai
  value: gpt-4o-mini
type:
  coding:
  - system: http://hl7.org/fhir/uv/aitransparency/CodeSystem/AIdeviceTypeCS
    code: Artificial-Intelligence
extension:
- url: http://hl7.org/fhir/uv/aitransparency/StructureDefinition/aitransparency.AIKind
  valueCodeableConcept:
    coding:
    - system: http://hl7.org/fhir/uv/aitransparency/CodeSystem/AIdeviceTypeCS
      code: Large-Language-Models
manufacturer: openai
modelNumber: gpt-4o-mini
deviceName:
- name: gpt-4o-mini
  type: model-name

AIKind is Large-Language-Models for an LLM and AI-for-Audio-Data for speech models. identifier is urn:sdc:voice:{provider}|{model}, which is how an existing Device is found instead of duplicated.

Place a call — $call

Creates the Task that owns the conversation and places (or schedules) the phone call. Phone is the default. The workflow owns retries (maxAttempts, retryDelayMinutes on the agent).

Integrations use the default, phone.

URLs

POST [base]/sdc/voice/$call

Parameters

ParameterCardinalityTypeNotes
agent1..1Reference(SDCVoiceAgent) or SDCVoiceAgentA reference runs a stored agent. A whole resource runs a draft without saving
transport0..1stringphone (default). browser is used by the Formbox tester
context0..*BackboneOne {name, content} per slot the agent declares. All required
to0..1stringPhone only. E.164 or SIP. Defaults to the patient's first phone
send-at0..1instantPhone only. Schedule the dial instead of dialling now

Passing to or send-at with transport: browser is refused.

context

Same shape as $populate launch context: repeating parameter of {name, content} pairs.

- name: context
  part:
  - name: name
    valueString: patient
  - name: content
    valueReference:
      reference: Patient/123

Output

ParameterCardinalityTypeNotes
task1..1Reference(Task)Created Task
status1..1stringTask status
call-id1..1stringConversation id
operation-id0..1stringScheduled phone calls only

Example

POST /sdc/voice/$call
content-type: text/yaml
accept: text/yaml

resourceType: Parameters
parameter:
- name: transport
  valueString: phone
- name: agent
  valueReference:
    reference: SDCVoiceAgent/abc
- name: to
  valueString: '+15551234567'
- name: context
  part:
  - name: name
    valueString: patient
  - name: content
    valueReference:
      reference: Patient/123
resourceType: Parameters
parameter:
- name: task
  valueReference:
    reference: Task/def
- name: status
  valueString: requested
- name: call-id
  valueString: 0c1a2b3c-4d5e-6f70-8899-aabbccddeeff

Errors

OperationOutcome when: agent missing or not an SDCVoiceAgent, agent not active, agent with no questionnaire, bad send-at, unsupplied / unresolvable / mistyped context, no Patient slot for a phone call, no phone number, or Twilio misconfiguration.

End a call — $hangup

Ends a live phone call. Body is JSON, not Parameters.

URLs

POST [base]/sdc/voice/$hangup
{"call": "<call-id>"}

404 if there is no live call with that id.

Run results

Returns the transcript, QuestionnaireResponses, Provenance, any downstream extracted resources, and the Task.

This reads the Task; it does not wait. Extraction runs after the call ends, so a request made too early returns a Task whose output is not filled in yet — poll, or watch the Task.

URLs

GET [base]/sdc/voice/run/{call-id}

404 if there is no call with that id.

What a call writes

The Task is created first (status: requested).

Phone callTest run
intentorderproposal
codevoice-callvoice-rehearsal

identifier holds the call-id. focus is the first questionnaire. for is the Patient when there is one. A draft agent sent whole is contained on the Task.

After hangup, extraction writes resources and lists them on Task.output:

type.textResource
transcriptDocumentReference
questionnaire-responseQuestionnaireResponse (one per extractable questionnaire)
provenanceProvenance (omitted when there is no extractor Device)
extractedWhat SDC $extract made of a completed response

Completed responses go through $submit. Partial ones (in-progress) are stored as they are. A call with no answers still writes Task.output, so it is not retried as a missed call.

Answers and their evidence

Each QuestionnaireResponse.item carries what backs its answer, so an answer can be checked without re-reading the transcript. An item the conversation never settled is still present, saying why it is empty.

ExtensionTypeMeaning
.../sdc-voice/answer-statuscodeWhether the person stated it or it was inferred
.../sdc-voice/evidencestringThe words the answer was read from
.../sdc-voice/source-turnstringWhich turn they were said in, tagged [P1], [P2] in the transcript
AIconfidencedecimalHow sure the model was, 0‑1

The first three use the base http://health-samurai.io/fhir/StructureDefinition/sdc-voice.

resourceType: QuestionnaireResponse
questionnaire: http://example.org/Questionnaire/discharge-symptoms
status: completed
authored: '2026-03-04T10:21:44Z'
subject:
  reference: Patient/123
item:
- linkId: chest-pain
  text: Any chest pain since you went home?
  extension:
  - url: http://health-samurai.io/fhir/StructureDefinition/sdc-voice/answer-status
    valueCode: stated
  - url: http://health-samurai.io/fhir/StructureDefinition/sdc-voice/evidence
    valueString: no, nothing like that
  - url: http://health-samurai.io/fhir/StructureDefinition/sdc-voice/source-turn
    valueString: P2
  - url: http://hl7.org/fhir/uv/aitransparency/StructureDefinition/AIconfidence
    valueDecimal: 0.96
  answer:
  - valueBoolean: false

Provenance marks the record as AI-asserted (AIAST), names the extractor Device as author with role Artificial-Intelligence, and lists the person who spoke as informant — the call has no human author, so the model is the author and the patient is the source of the answers. Its entity cites the transcript and carries both prompts — the one that drove the conversation and the one that read it back into answers — as contained AI-InputPrompt documents.

Settings

IDEnvironment variable
module.sdc.openai-api-keyBOX_MODULE_SDC_OPENAI_API_KEY / BOX_SDC_OPENAI_API_KEY
module.sdc.openai-base-urlBOX_MODULE_SDC_OPENAI_BASE_URL / BOX_SDC_OPENAI_BASE_URL
modules.sdc.gemini-api-keyBOX_SDC_GEMINI_API_KEY
module.sdc.deepgram-api-keyBOX_MODULE_SDC_DEEPGRAM_API_KEY / BOX_SDC_DEEPGRAM_API_KEY
module.sdc.cartesia-api-keyBOX_MODULE_SDC_CARTESIA_API_KEY / BOX_SDC_CARTESIA_API_KEY
module.sdc.elevenlabs-api-keyBOX_MODULE_SDC_ELEVENLABS_API_KEY / BOX_SDC_ELEVENLABS_API_KEY
module.sdc.twilio-account-sidBOX_MODULE_SDC_TWILIO_ACCOUNT_SID / BOX_SDC_TWILIO_ACCOUNT_SID
module.sdc.twilio-auth-tokenBOX_MODULE_SDC_TWILIO_AUTH_TOKEN / BOX_SDC_TWILIO_AUTH_TOKEN
module.sdc.twilio-from-numberBOX_MODULE_SDC_TWILIO_FROM_NUMBER / BOX_SDC_TWILIO_FROM_NUMBER

Gemini's setting id is modules.sdc.gemini-api-key (plural modules) and has no BOX_MODULE_SDC_* alias.

Last updated: