Phone Number
A single telephony number wired into your flow — one element that both receives inbound SMS and calls and sends them back out, from a quick test SMS on a leased sandbox number all the way to a fully conversational voice agent answering the line.
Working with it
Selecting a Phone Number reveals its settings in the properties panel; it has no dedicated full-screen workbench.
How it appears
The same element type rendered as a definition, a circle instance, and a live workspace card.
When to use / not
When to use
- Sending transactional or notification SMS from a flow — alerts, confirmations, one-time codes — with automatic multi-part concatenation and an optional alphanumeric sender ID.
- Standing up SMS sending fast with no carrier account: lease a Triform-managed sandbox number (claim_sandbox_number) and send immediately, no credential_ref required.
- Giving a Triform agent a real phone line — inbound calls route to a bound telephony_agent (STT → brain → TTS), and agent-call / call-message place conversational or one-shot spoken outbound calls.
- Receiving inbound SMS or calls on a provisioned number and feeding them into a flow via the message / call_event output ports.
When not to use
- Chat or team messaging rather than the public telephone network — use the slack, teams, discord, or matrix io connectors instead.
- Plain transactional email — use the email element; phone-number is SMS and voice over a carrier, not mailbox delivery.
- Expecting inbound replies on a sandbox number — sandbox leases are outbound-only in v1; provision a dedicated carrier number for two-way SMS or voice.
Topology
Created from the library and placed inside an app or circle. It is a top-level building block you compose with other elements.
Properties
credential_refstring- Reference to secret element containing carrier API credentials (e.g. 46elks API username and password). Not needed when sandbox_managed is true.
sandbox_managedboolean- When true, this element uses Triform's managed sandbox-number pool (configured via SANDBOX_PHONE_POOL on the deployment). credential_ref is not required in this mode — outbound SMS uses the platform 46elks account, throttled per-circle to a daily cap (default 10 SMS/day). Set automatically by ops/claim_sandbox_number; do not toggle by hand. Sandbox numbers are outbound-only in v1; inbound replies are not routed.
phone_numberstring- Phone number in E.164 format (e.g. +46766861004)
countrystring- ISO 3166-1 alpha-2 country code (e.g. SE, US, GB)
friendly_namestring- Human-readable label for this number
capabilitiesobject- Enabled capabilities for this number
default_sender_idstring- Default alphanumeric sender ID for outbound SMS (carrier-dependent, max 11 chars)
flash_smsboolean- Send as flash SMS (displayed immediately, not stored) by default
caller_idstring- Caller ID for outbound calls (defaults to phone_number)
voice_start_urlstring- Webhook URL for voice call flow (IVR actions)
webrtc_sip_userstring- SIP username for the 46elks WebRTC endpoint (see 46elks dashboard)
webrtc_sip_passstring- SIP password for the 46elks WebRTC endpoint — stored encrypted
telephony_agentstring- Agent that answers and originates calls on this number. When set, calls are handled headlessly by physics via SIP; the browser never rings. Reassigning this field starts a NEW conversation thread under the new agent for the next call from any contact — different agent means different LLM context, so calls before and after the rebind don't bleed into one history. Voice transcripts and SMS for the previous agent stay readable in their original thread; only the per-(contact, agent) keying for new traffic is affected. (See migration 0081 + persistence_consumer Phase B.)
incoming_handlingstring- How incoming calls on this number should be handled. 'ai' routes to the configured telephony_agent via the 46elks Realtime Voice WebSocket bridge — the platform acquires a pool slot at call time and the agent answers with its own ears/brain/mouth. 'forward_to' bridges the call to forward_to_number via 46elks. The choice is orthogonal to outbound: the browser's SIP.js client can always originate calls from this number regardless of which inbound mode is selected.
forward_to_numberstring- E.164 phone number to bridge inbound calls to when incoming_handling = forward_to (e.g. +46701234567).
carrierstring- Carrier provider identifier
max_retriesinteger- Maximum delivery retry attempts
retry_backoff_msinteger- Base backoff between retries in milliseconds (exponential)
Capabilities
Inherited from io
- Network
- Observe
Operations
- activityGET
- agent-callPOST
- agent-dialPOST
- attachmentsGET
- batch_statsGET
- call-messagePOST
- claim_sandbox_numberPOST
- composePOST
- configurePOST
- contextGET
- createPOST
- deleteDELETE
- disablePOST
- enablePOST
- export_bundleGET
- getGET
- get-callGET
- get-smsGET
- import_bundlePOST
- intentionGET
- list-callsGET
- list-smsGET
- make-callPOST
- promotePOST
- provisionPOST
- readmeGET
- readme_updatePOST
- receivePOST
- releasePOST
- release_sandbox_numberPOST
- remove-modifierPOST
- restorePOST
- schemaGET
- sendPOST
- send-smsPOST
- sip-register-testPOST
- sourceGET
- source_branchesGET
- source_promotePOST
- source_repairPOST
- source_statusGET
- source_validatePOST
- statsGET
- test_connectionPOST
- treeGET
- updatePATCH
- update_metaPATCH
- versionGET
Ports
Inputs
- triggerevent
- call_triggerevent
- requestrequest
- call_requestrequest
- messageevent
- resultevent
- call_eventevent
Composition
Errors / when it fails
- phone_number must be in E.164 format (e.g. +46766861004)
- Fails unless:
phone_number == null || phone_number == "" || phone_number.matches("^\\+[1-9]\\d{1,14}$") - country must be a 2-letter ISO 3166-1 alpha-2 code (e.g. SE, US, GB)
- Fails unless:
country == null || country == "" || country.matches("^[A-Z]{2}$")
Validation rules
- Voice is enabled but no voice_start_url is configured — inbound calls will have no IVR flow
Phone Number (phone-number)
Category: io | Form: | Symbol: Ph
Connect flows to telephony for sending and receiving SMS and voice calls via carrier APIs
Unified telephony connector — receives and sends SMS or voice calls depending on wiring topology. When the trigger port is exposed (unwired input), acts as a receiver: inbound SMS and calls matching the provisioned number are delivered via NATS and emitted on the message output port. When the request port is driven (wired from another element), acts as a sender: composes and sends SMS or initiates calls via the configured carrier API. Configure spec.credential_ref with a secret element containing carrier API credentials. spec.phone_number holds the E.164-formatted number (e.g. +46766861004). spec.country sets the ISO 3166-1 alpha-2 country code for number validation and formatting. Supports SMS (send/receive), voice calls (initiate/receive), and carrier-level features like delivery receipts and caller ID. Common mistake: missing credential_ref — send operations will fail.
Guide
Connect flows to telephony for sending and receiving SMS and voice calls via carrier APIs
What It Does
Phone-number is an IO connector that bridges Triform flows with telephony. It is a unified send/receive element: when the trigger port is left unwired it acts as a receiver — inbound SMS and calls matching the provisioned number are delivered via NATS and emitted on the message / call output ports. When the request port is driven from another element it acts as a sender — it composes and sends SMS or initiates calls via the configured carrier API. The default carrier is 46elks.
The number itself is the element’s identity: instances are auto-named and slugged after the E.164 phone_number (e.g. +46766861004). Numbers are obtained either by leasing a Triform-managed sandbox number (claim_sandbox_number, no credentials required, outbound-only, daily-capped) or by provisioning a dedicated carrier number (provision, requires credential_ref). SMS supports alphanumeric sender-ID override, flash SMS, and automatic concatenation of multi-part messages.
Voice supports several outbound paths: a plain call (make-call, via the bound telephony_agent SIP UA), a one-shot spoken TTS announcement (call-message, synthesized through a bound mouth element), and a fully conversational agent-driven call (agent-call, the outbound mirror of the inbound agent flow). Inbound calls are handled per incoming_handling: ai routes to the configured telephony_agent, forward_to bridges to forward_to_number.
Element Definition
| Property | Value |
|---|---|
| Type | phone-number |
| Category | io |
| Form | atom |
| Handler | PhoneNumberHandler |
| Activity type | connector |
| Delivery mode | async |
| Retry policy | exponential (max 3) |
| Streaming | not supported |
| Default carrier | 46elks |
Key Properties
| Field | Type | Default | Description |
|---|---|---|---|
credential_ref | string | — | Reference to a secret element holding carrier API credentials. Not needed when sandbox_managed is true. |
sandbox_managed | boolean | false | Use Triform’s managed sandbox-number pool. Set automatically by claim_sandbox_number; do not toggle by hand. |
phone_number | string | — | The number in E.164 format (e.g. +46766861004). Max 16 chars. |
country | string | — | ISO 3166-1 alpha-2 country code (e.g. SE, US, GB). Max 2 chars. |
friendly_name | string | — | Human-readable label for this number. |
capabilities.sms | boolean | true | SMS send and receive enabled. |
capabilities.voice | boolean | false | Voice call initiate and receive enabled. |
default_sender_id | string | — | Default alphanumeric sender ID for outbound SMS (max 11 chars). |
flash_sms | boolean | false | Send as flash SMS (displayed immediately, not stored) by default. |
caller_id | string | — | Caller ID for outbound calls (defaults to phone_number). |
voice_start_url | string | — | Webhook URL for the inbound voice call flow (IVR actions). |
webrtc_sip_user | string | — | SIP username for the 46elks WebRTC endpoint. |
webrtc_sip_pass | string | — | SIP password for the 46elks WebRTC endpoint (stored encrypted). |
telephony_agent | string | — | Agent (triformer) that answers and originates calls headlessly via SIP. When set, the browser never rings. |
incoming_handling | string | ai | How inbound calls are handled: ai (route to telephony_agent) or forward_to. |
forward_to_number | string | — | E.164 number to bridge inbound calls to when incoming_handling = forward_to. |
carrier | string | 46elks | Carrier provider identifier. |
max_retries | integer | 3 | Maximum delivery retry attempts (0–10). |
retry_backoff_ms | integer | 2000 | Base exponential backoff between retries in ms (500–60000). |
Ports
Inbound (trigger ports exposed = receiver; request ports driven = sender):
| Port | Direction | Type | Description |
|---|---|---|---|
trigger | input | event | Inbound SMS to the provisioned number (SmsReceived). |
call_trigger | input | event | Inbound voice call to the provisioned number (CallReceived). |
request | input | request | SMS to send (SmsSendRequest). |
call_request | input | request | Voice call to initiate (CallRequest). |
message | output | event | Parsed inbound SMS forwarded into the flow (SmsReceived). |
result | output | event | Send result with message ID and delivery status (SmsSendResponse). |
call_event | output | event | Call event with call ID and status updates (CallResponse). |
Capabilities
sms-send, sms-receive, voice-call, voice-receive, delivery-receipts, flash-sms, alphanumeric-sender, concatenated-sms.
Attaches: rate-limit, auth-policy. Uses: variable.
Error Codes
| Code | Class | Retryable | Description |
|---|---|---|---|
PHONE_AUTH_FAILED | auth | no | Carrier API authentication failed (invalid credentials). |
PHONE_SEND_FAILED | internal | yes | Failed to send SMS via carrier API. |
PHONE_INVALID_NUMBER | validation | no | Recipient phone number is not valid E.164. |
PHONE_REJECTED | internal | no | Carrier rejected the message (permanent failure). |
PHONE_RATE_LIMITED | internal | yes | Carrier rate limit exceeded. |
PHONE_NUMBER_NOT_PROVISIONED | validation | no | Phone number is not provisioned or active with the carrier. |
PHONE_CREDENTIAL_MISSING | auth | no | credential_ref not set or referenced secret not found. |
PHONE_CALL_FAILED | internal | yes | Failed to initiate voice call. |
PHONE_INSUFFICIENT_BALANCE | internal | no | Carrier account balance insufficient for this operation. |
Operations
send-sms — POST send-sms (auth: write)
Sends an SMS via the configured carrier API. Requires credential_ref and phone_number. Recipient to must be valid E.164. Supports alphanumeric sender_id override (max 11 chars, passed as the from field to 46elks). Multi-part messages are concatenated automatically for bodies over 160 GSM-7 (or 70 UCS-2) characters. Input: to, body (required), sender_id. Output: sent, message_id, segments, cost.
make-call — POST make-call (auth: write)
Places an outbound SIP call via the bound telephony_agent. Requires phone_number provisioned AND a live TelephonyManager registration for this replica. Delegates to agent-dial internally; the call uses the live SIP connector (not the 46elks HTTP API), so caller_id and ring timeout are not overridable per-call. Input: to (required). Output: initiated, call_id.
call-message — POST call-message (auth: write)
One-shot spoken announcement. Synthesizes message through a bound mouth element, stores the MP3 at a short-lived public URL, then POSTs to 46elks with voice_start={"play": <url>}; the recipient hears the message once and the call hangs up. No IVR, no conversation. Requires phone_number (or caller_id override) and a mouth (spec.mouth_ref or input.mouth_ref). Input: to, message (required, max 1200 chars), caller_id, mouth_ref, voice_id, whenhangup_url. Output: call_id, state, audio_url, audio_token_ttl_seconds.
agent-call — POST agent-call (auth: write)
Outbound conversational call driven by this number’s bound agent — the outbound mirror of the inbound agent flow. POSTs to 46elks /calls with voice_start={"connect": "wss://<host>/api/webhooks/46elks/voice/ws"}; when the peer answers, the WebSocket bridge resolves the bound agent and runs the same VAD → STT → brain → streaming-TTS pipeline as inbound. Requires phone_number + credential_ref provisioned AND a telephony_agent / mouth / ears bound. Input: to (required), caller_id, whenhangup_url. Output: call_id, state, ws_url.
claim_sandbox_number — POST claim_sandbox_number (auth: write)
Leases a Triform-managed sandbox number from the platform pool (configured via SANDBOX_PHONE_POOL) for outbound SMS testing. After it returns, send-sms works against this element with no carrier credentials and no per-circle 46elks account. Idempotent. Sandbox numbers are outbound-only in v1; inbound replies do not route back. Per-circle daily cap (default 10 SMS/day, SANDBOX_PHONE_SMS_DAILY_CAP). For production sending, use provision instead. Input: none. Output: claimed, sandbox_managed, phone_number, lease_id, claimed_at, pool, limits, next_step.
release_sandbox_number — POST release_sandbox_number (auth: write)
Drops the active sandbox lease for this element, clearing sandbox_managed and phone_number. Safe to call when no lease is active (no-op returning released:false). Future sends fall back to the credential_ref / provision path. Input: none. Output: released, leases_released.
provision — POST provision (auth: write)
Allocates a new phone number from the carrier for this element, based on country and available inventory. phone_number is updated automatically afterward. Requires credential_ref. Input: country, capabilities (array of sms / voice). Output: provisioned, phone_number.
release — POST release (auth: write)
Releases (deallocates) the provisioned number back to the carrier; it will no longer receive inbound SMS or calls. Cannot be undone — the number may be reassigned. Output: released.
configure — POST configure (auth: write)
Updates the carrier webhook URLs for inbound SMS and voice. Called automatically during provisioning; re-run to update endpoints (e.g. after a domain change). Input: sms_url, voice_start_url. Output: configured.
list-sms — GET list-sms (auth: read)
Lists SMS sent and received by this number; cursor-paginated via page_token. Returns metadata (id, from, to, direction, status, date) without full bodies. Input: direction (default all), page_token, limit (1–100, default 50). Output: messages, total, next_page_token.
get-sms — GET get-sms (auth: read)
Retrieves the full SMS including body, delivery status, and carrier metadata. Input: message_id (required). Output: id, from, to, body, direction, status, created_at, segments, cost.
list-calls — GET list-calls (auth: read)
Lists voice calls made and received by this number; cursor-paginated via page_token. Input: direction (default all), page_token, limit (1–100, default 50). Output: calls, total, next_page_token.
get-call — GET get-call (auth: read)
Retrieves the full call record including duration, status, recording URL (if available), and carrier metadata. Input: call_id (required). Output: id, from, to, direction, status, duration, started_at, ended_at, recording_url, cost.
test_connection — POST test-connection (auth: execute)
Connects to the carrier API and verifies authentication. Returns account status, balance, and number details. Sends nothing. Output: connected, authenticated, account_balance, number_status, error.
sip-register-test — POST sip-register-test (auth: execute)
One-shot SIP REGISTER round-trip against the carrier — verifies that webrtc_sip_user / webrtc_sip_pass can authenticate a SIP UA against 46elks. Tears the registration down immediately (does not keep it alive). Input: sip_user, sip_pass (overrides for first-run testing). Output: success, status, expires, error.
agent-dial — POST agent-dial (auth: execute)
Triggers the headless SIP UA to dial to from this number’s registered SIP user; the agent runs the standard STT → brain → TTS loop when the callee answers. Only works with telephony_agent set AND a live TelephonyManager registration. Input: to (required). Output: success, sip_call_id, error.
Quick Start
- Create the element and lease a sandbox number (no carrier credentials needed):
triform_ops(action: "call", slug: "{slug}", operation: "claim_sandbox_number")
- Send a test SMS against the leased number:
triform_ops(
action: "call",
slug: "{slug}",
operation: "send-sms",
input: { to: "+46700000000", body: "Hello from Triform" }
)
- For production sending instead, configure
credential_refwith a secret holding 46elks API credentials, thenprovisiona dedicated number and verify withtest-connection.
Common Mistakes
- Missing
credential_ref. Send operations fail withPHONE_CREDENTIAL_MISSINGunless the element is in sandbox mode. Either setcredential_refto a carrier-credentials secret, or useclaim_sandbox_number(which setssandbox_managed). - Non-E.164 numbers.
phone_numbermust match^\+[1-9]\d{1,14}$(e.g.+46766861004); recipienttovalues that aren’t valid E.164 producePHONE_INVALID_NUMBER.countrymust be a 2-letter ISO 3166-1 alpha-2 code (e.g.SE,US,GB). - Sending before provisioning. A number that isn’t provisioned/active returns
PHONE_NUMBER_NOT_PROVISIONED. Runprovision(orclaim_sandbox_number) first. - Expecting inbound replies on a sandbox number. Sandbox numbers are outbound-only in v1 — inbound replies are not routed back to the calling circle. Provision a dedicated number for receive.
- Voice without a flow. Enabling
capabilities.voicewithout settingvoice_start_urlraises a warning — inbound calls will have no IVR flow. telephony_agentvoice ops without a live SIP registration.make-call/agent-dialrequire bothtelephony_agentset and a liveTelephonyManagerregistration; validate credentials first withsip-register-test.
Relationships
- Attaches to: rate-limit, auth-policy
- Uses: variable
Capabilities
- sms-send: Send SMS via carrier API
- sms-receive: Receive inbound SMS via webhook delivery pipeline
- voice-call: Initiate outbound voice calls
- voice-receive: Receive inbound voice calls via webhook
- delivery-receipts: Track SMS delivery status via carrier webhooks
- flash-sms: Send flash SMS (displayed immediately, not stored)
- alphanumeric-sender: Custom alphanumeric sender ID for outbound SMS
- concatenated-sms: Automatic multi-part SMS for long messages
Properties
| Property | Type | Default | Description |
|---|---|---|---|
credential_ref | string | — | Reference to secret element containing carrier API credentials (e.g. 46elks API username and password). Not needed when sandbox_managed is true. |
sandbox_managed | boolean | false | When true, this element uses Triform’s managed sandbox-number pool (configured via SANDBOX_PHONE_POOL on the deployment). credential_ref is not required in this mode — outbound SMS uses the platform 46elks account, throttled per-circle to a daily cap (default 10 SMS/day). Set automatically by ops/claim_sandbox_number; do not toggle by hand. Sandbox numbers are outbound-only in v1; inbound replies are not routed. |
phone_number | string | — | Phone number in E.164 format (e.g. +46766861004) |
elks_number_id | string | — | Internal carrier number id set by provision — do not edit. Used by release / configure / test-connection ops to address the number at the carrier. |
country | string | — | ISO 3166-1 alpha-2 country code (e.g. SE, US, GB) |
friendly_name | string | — | Human-readable label for this number |
capabilities | object | — | Enabled capabilities for this number |
default_sender_id | string | — | Default alphanumeric sender ID for outbound SMS (carrier-dependent, max 11 chars) |
flash_sms | boolean | false | Send as flash SMS (displayed immediately, not stored) by default |
caller_id | string | — | Caller ID for outbound calls (defaults to phone_number) |
voice_start_url | string | — | Webhook URL for voice call flow (IVR actions) |
webrtc_sip_user | string | — | SIP username for the 46elks WebRTC endpoint (see 46elks dashboard) |
webrtc_sip_pass | string | — | SIP password for the 46elks WebRTC endpoint — stored encrypted |
telephony_agent | string | — | Agent that answers and originates calls on this number. When set, calls are handled headlessly by physics via SIP; the browser never rings. Reassigning this field starts a NEW conversation thread under the new agent for the next call from any contact — different agent means different LLM context, so calls before and after the rebind don’t bleed into one history. Voice transcripts and SMS for the previous agent stay readable in their original thread; only the per-(contact, agent) keying for new traffic is affected. (See migration 0081 + persistence_consumer Phase B.) |
incoming_handling | string | "ai" | How incoming calls on this number should be handled. ‘ai’ routes to the configured telephony_agent via the 46elks Realtime Voice WebSocket bridge — the platform acquires a pool slot at call time and the agent answers with its own ears/brain/mouth. ‘forward_to’ bridges the call to forward_to_number via 46elks. The choice is orthogonal to outbound: the browser’s SIP.js client can always originate calls from this number regardless of which inbound mode is selected. |
forward_to_number | string | — | E.164 phone number to bridge inbound calls to when incoming_handling = forward_to (e.g. +46701234567). |
carrier | string | "46elks" | Carrier provider identifier |
carrier_api_url | string | "" | Carrier API base URL (defaults to provider standard) |
max_retries | integer | 3 | Maximum delivery retry attempts |
retry_backoff_ms | integer | 2000 | Base backoff between retries in milliseconds (exponential) |
Operations
activity
Get /ops/activity | Auth: Read
Get activity events for this element
Scope depends on element capabilities: individual elements query by element_id, project-form elements with activity-scope-members include member activities, circle-level elements with activity-scope-all query the entire circle. Gracefully returns empty list if activities table is missing (old circles).
agent-call
Post /ops/agent-call | Auth: Write
Place an outbound conversational call driven by this number’s bound agent
Outbound mirror of the inbound agent flow. POSTs to 46elks
/callswithvoice_start={"connect": "wss://<host>/api/webhooks/46elks/voice/ws"}— the same WebSocket URL inbound calls hit. When the peer picks up, the WebSocket bridge looks uphello.callidincall_records(row is inserted before the peer answers), resolves this element’s bound agent, and runs the identical VAD → STT → brain → streaming TTS pipeline as inbound. Direction never enters the bridge; only the opening prompt branches so the agent introduces itself rather than greets. Requiresspec.phone_number+spec.credential_refto already be provisioned, AND a telephony_agent / mouth / ears bound on this number’s agent binding.
agent-dial
Post /ops/agent-dial | Auth: Execute
Place an outbound call via the bound telephony_agent
Triggers the headless SIP UA to dial
to(E.164) from this phone-number’s registered SIP user. The agent answers when the callee picks up and runs the standard STT → brain → TTS conversation loop. Only works on phones withtelephony_agentset AND a live TelephonyManager registration (seesip-register-testfor credentials setup).
attachments
Get /ops/attachments | Auth: Read
List all modifiers and resources attached to this element
Returns both modifiers (policy enforcement) and resources (data injection) with is_modifier flag to distinguish. Items in the generated MODIFIER_TYPES list are modifiers; everything else is a resource. Includes cascade_policy and version pin info.
batch_stats
Get /ops/batch_stats | Auth: Read
Get per-element statistics for all children of this element
Returns per-child stats plus an aggregate. Most meaningful on compound or manifest form elements (repositories, circles, projects); atoms have no children so the result is an empty children array with a zeroed aggregate. Uses efficient GROUP BY SQL. Weighted averages for eval scores.
call-message
Post /ops/call-message | Auth: Write
Place an outbound call and play a TTS announcement
One-shot spoken announcement. Synthesizes
messagethrough a bound mouth element (Mistral Voxtral or similar), stores the resulting MP3 at a short-lived public URL, then POSTs to 46elks with voice_start={“play”:}. The recipient answers, hears the message once, and the call hangs up. No IVR, no agent, no conversation. Requires spec.phone_number (or caller_id override), and either spec.mouth_ref or input.mouth_ref to identify which mouth to use.
claim_sandbox_number
Post /ops/claim_sandbox_number | Auth: Write
Lease a Triform-managed sandbox number for testing
Triform owns a small pool of E.164 numbers (configured via SANDBOX_PHONE_POOL on the deployment) that any circle can lease for outbound SMS testing. After this op returns, ops/send-sms works against this element with no further configuration — no carrier credentials, no DNS, no per-circle 46elks account. Idempotent: safe to call repeatedly. Sandbox numbers are outbound-only in v1; inbound replies do not route back to the calling circle. Per-circle daily cap (default 10 SMS/day) protects the platform 46elks account from abuse — see SANDBOX_PHONE_SMS_DAILY_CAP env to override. For long-term production sending, use ops/provision instead (allocates a dedicated carrier number to your circle, requires credential_ref or platform creds).
compose
Post /ops/compose | Auth: Execute
Batch add and remove modifiers on this element in a single call
Declarative composition: add modifiers by ref path (slug or path@version) and remove by attachment ID, all in one atomic call on the target element. Each ‘add’ entry resolves the source element, validates topology, attaches with optional priority and cascade policy. Each ‘remove’ entry deletes the attachment row. Returns a summary of what was added and removed. Example: compose({ add: [{ref: “my-prompt”}, {ref: “rate-limit/api@v2”, priority: 50}], remove: [{attachment_id: “uuid”}] })
configure
Post /ops/configure | Auth: Write
Configure carrier webhooks for the phone number
Updates the carrier webhook URLs for inbound SMS and voice calls. This is called automatically during provisioning but can be re-run to update webhook endpoints (e.g. after a domain change).
context
Get /ops/context | Auth: Read
Get connected elements (graph traversal)
Graph traversal showing all connected elements with their relationship type (contains, contained_by, references, referenced_by, attaches, etc.). Use ?depth=N to control traversal depth (default 1) and ?types=actor,data to filter by element types.
create
Post /ops/create | Auth: Write
Create child element
POST to the parent path — element_type goes in the request body, NOT the URL. Both element_type and slug are required and must be non-empty. Name is derived from slug if omitted. Writes to both Git and PostgreSQL. All elements are stored flat under the circle — no intermediate library wrapper rows.
delete
Delete /ops/delete | Auth: Admin
Delete element (soft delete)
Soft delete — sets state to ‘deleted’ but retains the record. Cannot delete elements that have children (has_no_bond precondition) or active runs. Requires admin auth and confirmation.
disable
Post /ops/disable | Auth: Admin
Disable element (hides and prevents use)
Idempotent — safe to call on already-disabled elements. Optionally pass a reason string. Disabled elements cannot be invoked or executed. Inverse of enable.
enable
Post /ops/enable | Auth: Admin
Enable element (makes usable and visible)
Idempotent — safe to call on already-enabled elements. Transitions element to ready/enabled state. Cannot enable deleted elements. Inverse of disable.
export_bundle
Get /ops/export/bundle | Auth: Read
Export element as downloadable git bundle
On non-root-namespace elements, returns a binary git bundle. On root-namespace (circle) elements, dispatch hands off to the circle’s own export_bundle op, which returns a multi-element JSON envelope with one base64 bundle per child element — this is intentional, not an error.
get
Get /ops/get | Auth: Read
Get element details
Element is already resolved by the routing layer — this returns the cached element, not a fresh DB query. Use the path /api/{circle}/{slug} to address elements.
get-call
Get /ops/get-call | Auth: Read
Get details of a single voice call
Retrieves the full call record including duration, status, recording URL (if available), and carrier metadata.
get-sms
Get /ops/get-sms | Auth: Read
Get a single SMS message
Retrieves the full SMS message including body, delivery status, and carrier metadata.
import_bundle
Post /ops/import/bundle | Auth: Write
Import git bundle into element
Accepts a base64-encoded git bundle in the JSON bundle_base64 field. Use overwrite=true to replace existing elements with same slug (default skips duplicates). Imported elements get new UUIDs. Returns counts of imported/skipped elements and any errors.
intention
Get /ops/intention | Auth: Read
Get element intention with full inheritance chain
Returns three levels: direct (this element’s intention), inherited (from category and root), and resolved (final merged intention). Useful for understanding an element’s purpose in context of its hierarchy.
list-calls
Get /ops/list-calls | Auth: Read
List voice calls
Lists voice calls made and received by this number. Paginated with page_token for cursor-based navigation. Returns call metadata (id, from, to, direction, status, duration, date).
list-sms
Get /ops/list-sms | Auth: Read
List SMS messages
Lists SMS messages sent and received by this number. Paginated with page_token for cursor-based navigation. Returns message metadata (id, from, to, direction, status, date) without full bodies.
make-call
Post /ops/make-call | Auth: Write
Initiate a voice call
Places an outbound SIP call via the bound telephony_agent. Requires spec.phone_number to be provisioned AND a live TelephonyManager registration for this replica. Delegates to agent-dial internally — the call uses the live SIP connector (not the 46elks HTTP API), so caller_id and ring timeout are not overridable per-call; they are determined by the SIP UA and the carrier’s registration. Use agent-call if you need the 46elks HTTP path with caller_id control.
promote
Post /ops/promote | Auth: Admin
Promote element configuration to a target environment
Only for manifest-form elements (projects). Environments advance: dev → demo → live. dev→demo requires member+ role, demo→live requires admin. Freezes member versions at promotion time (creates snapshot). Persists environment config to spec.environments.
provision
Post /ops/provision | Auth: Write
Provision a phone number from the carrier
Allocates a new phone number from the carrier for this element. The number is assigned based on spec.country and available inventory. After provisioning, spec.phone_number is updated automatically. Requires spec.credential_ref.
readme
Get /ops/readme | Auth: Read
Get element README.md content
Reads README.md from the element’s git repository. Returns empty content (not an error) if no README exists. Always returns markdown format.
readme_update
Post /ops/readme_update | Auth: Write
Update element README.md content
Creates or overwrites README.md in the element’s git repo. Commits to the draft branch. Content must be provided as a markdown string.
receive
Post /ops/receive | Auth: None
Receive incoming external traffic
Entry point for external traffic reaching this IO element. Declared auth: none to bypass platform auth — element-level auth is enforced by IoReceiveExecutor before dispatching into the flow graph. The flow/app that wires this element as an entry point determines what happens next.
release
Post /ops/release | Auth: Write
Release (deallocate) the provisioned phone number
Releases the phone number back to the carrier. The number will no longer receive inbound SMS or calls. This action cannot be undone — the number may be reassigned to another customer.
release_sandbox_number
Post /ops/release_sandbox_number | Auth: Write
Release this element’s sandbox number lease
Drops the active sandbox lease for this element, clears spec.sandbox_managed and spec.phone_number. Safe to call when no lease is active (no-op returning released:false). Future sends from this element fall back to the credential_ref / provision path.
remove-modifier
Post /ops/remove-modifier | Auth: Execute
Remove an attached modifier from this element by attachment ID
Removes a modifier/resource attachment by its row ID. The ID comes from the attachments or context API. This is the reverse of attach — called on the target element, not the source.
restore
Post /ops/restore | Auth: Admin
Restore element to a specific version
Automatically snapshots the current state before restoring (creates a ‘Before restore to vN’ version entry). Writes restored spec to git as .triform/spec.yaml. Git failures warn but don’t fail the operation — DB state is authoritative. Cannot restore deleted elements.
schema
Get /ops/schema | Auth: Read
Get element input/output schema (MCP tools/list compatible)
Returns type-level port schemas from the TypeRegistry — not instance-specific overrides. Includes direction (input/output), required flag, and JSON schema per port. Useful for understanding what data an element accepts and produces.
send
Post /ops/send | Auth: Execute
Send a message/request to external system
Explicitly sends payload to the configured external target. For HTTP elements, POSTs to the target URL. For chat platforms, sends via the platform API. Put data in the payload field. Returns send status and response details.
send-sms
Post /ops/send-sms | Auth: Write
Send an SMS message
Sends an SMS via the configured carrier API. Requires spec.credential_ref and spec.phone_number. Recipient must be a valid E.164 number. Supports alphanumeric sender ID override via sender_id (max 11 chars; passed as the
fromfield to 46elks). Concatenated (multi-part) messages are handled automatically for bodies exceeding 160 GSM-7 characters (or 70 UCS-2 characters).
sip-register-test
Post /ops/sip-register-test | Auth: Execute
One-shot SIP REGISTER round-trip against the carrier
Verifies that the configured WebRTC SIP credentials (
webrtc_sip_user/webrtc_sip_pass) can authenticate a SIP UA against 46elks. Used to validate setup before binding atelephony_agent. Tears down the registration immediately after the round-trip — does NOT keep a registration alive.
source
Get /ops/source | Auth: Read
Get any file’s content from the element’s git repository
Reads an arbitrary file from the element’s CAS-backed git tree by its relative path. Same store as
readme, just generalized. Path safety: rejects..traversal, leading/, and null bytes. Use this to viewmain.pyfor action elements, asset files for SPAs, etc. Returns empty content (not an error) if the file doesn’t exist.
source_branches
Get /ops/source/branches | Auth: Read
List Source branches for this element
Returns the standard draft/demo/live Source branches, their current commits, and promotion relationships. Use GET /api/{element_path}/ops/source/branches.
source_promote
Post /ops/source/promote | Auth: Write
Promote Source branch forward
Promotes draft to demo or demo to live through the generated element op path. Direct Git pushes to demo/live are blocked by Source policy.
source_repair
Post /ops/source/repair | Auth: Write
Inspect or repair the element Source index
Runs Source repair through the element operation path. Defaults to dry_run=true; set dry_run=false only after reviewing a dry-run report.
source_status
Get /ops/source/status | Auth: Read
Get Source control status for this element
Returns the branch-aware clone URL, checkout commands, current draft commit, child source-link count, portable export summary, Source health, warnings, and auth hints for the addressed element. Use the element-first path: GET /api/{element_path}/ops/source/status.
source_validate
Post /ops/source/validate | Auth: Read
Validate Source branch contents
Validates a Source branch before accepting local Git workflow changes or promotion. Defaults to branch=draft and rejects runtime data, generated output, secret material, and unreadable CAS refs.
stats
Get /ops/stats | Auth: Read
Get aggregate statistics for this element
Health status is computed: error if errors_per_day > 5 or success_rate < 0.8, warning if errors_per_day > 0 or success_rate < 0.95. Firing alerts escalate health to error/warning. Default period is ‘day’. Returns runs_per_day, success_rate, avg_duration_ms, and more.
test_connection
Post /ops/test-connection | Auth: Execute
Test carrier API connection and authentication
Connects to the configured carrier API and verifies authentication. Returns account status, balance, and provisioned number details. Does not send any messages.
tree
Get /ops/tree | Auth: Read
Get the element’s position in the graph — ancestors, children, references, and subtree statistics
Uses per-circle ElementGraph cache for O(1) lookups. Returns ancestors (containment chain), children (direct), members (references), referenced_by (reverse refs), attachments, and subtree stats. Default depth is 3, max is 10. Pass ?include_metadata=true for name/state on each node.
update
Patch /ops/update | Auth: Write
Update element
Partial update — send only the fields you want to change.
spec,name, andintentionare all independently optional.specMUST be a JSON object when present; deep-merged into the existing spec by default. Empty{"spec":{}}preserves existing spec content but still records a new version (no-op for content, not for version state). To clear/replace the entire spec wholesale send{"spec":{...},"deep":false}. List-typed spec fields use replace semantics (the patch list replaces the existing list, no array merging). Coordinates Git + DB writes. Slug cannot be changed after creation.
update_meta
Patch /ops/update_meta | Auth: Write
Update element metadata (lightweight merge — does NOT bump version or snapshot spec)
Shallow JSONB merge into element.meta. Top-level keys in the provided value replace existing meta values; other keys are preserved. Used for UI metadata like canvas positions, panel state, viewer preferences. Wire-shape op_name is
update_meta(distinct fromupdate) so SSE subscribers + the cache auto-invalidator can distinguish lightweight metadata changes from spec edits without inspecting the payload. The MutatingElementStore wrapper stamps this op_name on the lifecycle event emitted byupdate_element_metastorage calls.
version
Get /ops/version | Auth: Read
Get current version or full history
Returns current version by default. Pass ?history=true for full version history (up to ?limit=N, default 50). Versions are backed by the element_versions table. Every spec update creates a new version entry.
Error Codes
| Code | Class | Retryable | Description |
|---|---|---|---|
PHONE_AUTH_FAILED | auth | no | Carrier API authentication failed (invalid credentials) |
PHONE_SEND_FAILED | internal | yes | Failed to send SMS via carrier API |
PHONE_INVALID_NUMBER | validation | no | Recipient phone number is not valid E.164 |
PHONE_REJECTED | internal | no | Carrier rejected the message (permanent failure) |
PHONE_RATE_LIMITED | internal | yes | Carrier rate limit exceeded |
PHONE_NUMBER_NOT_PROVISIONED | validation | no | Phone number is not provisioned or active with the carrier |
PHONE_CREDENTIAL_MISSING | auth | no | credential_ref not set or referenced secret not found |
PHONE_CALL_FAILED | internal | yes | Failed to initiate voice call |
PHONE_INSUFFICIENT_BALANCE | internal | no | Carrier account balance insufficient for this operation |
Lifecycle / runtime
Inherited from io
Before request
- validate_auth
- check_rate_limit
After request
- record_metrics
On error
- log_error
- retry_if_transient
Execution model: async
Observability
Defined for this element
Metrics
- phone_sms_sent_count
- phone_sms_received_count
- phone_sms_send_latency_ms
- phone_sms_delivery_failure_count
- phone_call_initiated_count
- phone_call_received_count
- phone_call_duration_seconds
Events
- phone.sms.sent
- phone.sms.received
- phone.sms.failed
- phone.call.initiated
- phone.call.received
- phone.call.ended
- phone.call.failed
Pricing / cost
Defined for this element
Operation costs
- invoke: 10000 micro-AU
- send-sms: 50000 micro-AU
- make-call: 100000 micro-AU
Set it up
- Phone Numberstring
- Phone number in E.164 format (e.g. +46766861004)
- Carrier Credentialstring
- Secret element with carrier API credentials (e.g. 46elks username and password)
- Countrystring
- ISO 3166-1 alpha-2 country code (e.g. SE, US, GB)
- Capabilitiesstring
- Enable SMS and/or voice for this number