Download all docs
io

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.

Ph
type

Phone Number

Connect flows to telephony for sending and receiving SMS and voice calls via carrier APIs

ioatomdefinition

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

PropertyValue
Typephone-number
Categoryio
Formatom
HandlerPhoneNumberHandler
Activity typeconnector
Delivery modeasync
Retry policyexponential (max 3)
Streamingnot supported
Default carrier46elks

Key Properties

FieldTypeDefaultDescription
credential_refstringReference to a secret element holding carrier API credentials. Not needed when sandbox_managed is true.
sandbox_managedbooleanfalseUse Triform’s managed sandbox-number pool. Set automatically by claim_sandbox_number; do not toggle by hand.
phone_numberstringThe number in E.164 format (e.g. +46766861004). Max 16 chars.
countrystringISO 3166-1 alpha-2 country code (e.g. SE, US, GB). Max 2 chars.
friendly_namestringHuman-readable label for this number.
capabilities.smsbooleantrueSMS send and receive enabled.
capabilities.voicebooleanfalseVoice call initiate and receive enabled.
default_sender_idstringDefault alphanumeric sender ID for outbound SMS (max 11 chars).
flash_smsbooleanfalseSend as flash SMS (displayed immediately, not stored) by default.
caller_idstringCaller ID for outbound calls (defaults to phone_number).
voice_start_urlstringWebhook URL for the inbound voice call flow (IVR actions).
webrtc_sip_userstringSIP username for the 46elks WebRTC endpoint.
webrtc_sip_passstringSIP password for the 46elks WebRTC endpoint (stored encrypted).
telephony_agentstringAgent (triformer) that answers and originates calls headlessly via SIP. When set, the browser never rings.
incoming_handlingstringaiHow inbound calls are handled: ai (route to telephony_agent) or forward_to.
forward_to_numberstringE.164 number to bridge inbound calls to when incoming_handling = forward_to.
carrierstring46elksCarrier provider identifier.
max_retriesinteger3Maximum delivery retry attempts (0–10).
retry_backoff_msinteger2000Base exponential backoff between retries in ms (500–60000).

Ports

Inbound (trigger ports exposed = receiver; request ports driven = sender):

PortDirectionTypeDescription
triggerinputeventInbound SMS to the provisioned number (SmsReceived).
call_triggerinputeventInbound voice call to the provisioned number (CallReceived).
requestinputrequestSMS to send (SmsSendRequest).
call_requestinputrequestVoice call to initiate (CallRequest).
messageoutputeventParsed inbound SMS forwarded into the flow (SmsReceived).
resultoutputeventSend result with message ID and delivery status (SmsSendResponse).
call_eventoutputeventCall 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

CodeClassRetryableDescription
PHONE_AUTH_FAILEDauthnoCarrier API authentication failed (invalid credentials).
PHONE_SEND_FAILEDinternalyesFailed to send SMS via carrier API.
PHONE_INVALID_NUMBERvalidationnoRecipient phone number is not valid E.164.
PHONE_REJECTEDinternalnoCarrier rejected the message (permanent failure).
PHONE_RATE_LIMITEDinternalyesCarrier rate limit exceeded.
PHONE_NUMBER_NOT_PROVISIONEDvalidationnoPhone number is not provisioned or active with the carrier.
PHONE_CREDENTIAL_MISSINGauthnocredential_ref not set or referenced secret not found.
PHONE_CALL_FAILEDinternalyesFailed to initiate voice call.
PHONE_INSUFFICIENT_BALANCEinternalnoCarrier account balance insufficient for this operation.

Operations

send-smsPOST 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-callPOST 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-messagePOST 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-callPOST 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_numberPOST 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_numberPOST 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.

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

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

configurePOST 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-smsGET 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-smsGET 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-callsGET 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-callGET 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_connectionPOST 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-testPOST 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-dialPOST 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

  1. Create the element and lease a sandbox number (no carrier credentials needed):
triform_ops(action: "call", slug: "{slug}", operation: "claim_sandbox_number")
  1. Send a test SMS against the leased number:
triform_ops(
  action: "call",
  slug: "{slug}",
  operation: "send-sms",
  input: { to: "+46700000000", body: "Hello from Triform" }
)
  1. For production sending instead, configure credential_ref with a secret holding 46elks API credentials, then provision a dedicated number and verify with test-connection.

Common Mistakes

  • Missing credential_ref. Send operations fail with PHONE_CREDENTIAL_MISSING unless the element is in sandbox mode. Either set credential_ref to a carrier-credentials secret, or use claim_sandbox_number (which sets sandbox_managed).
  • Non-E.164 numbers. phone_number must match ^\+[1-9]\d{1,14}$ (e.g. +46766861004); recipient to values that aren’t valid E.164 produce PHONE_INVALID_NUMBER. country must 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. Run provision (or claim_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.voice without setting voice_start_url raises a warning — inbound calls will have no IVR flow.
  • telephony_agent voice ops without a live SIP registration. make-call / agent-dial require both telephony_agent set and a live TelephonyManager registration; validate credentials first with sip-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

PropertyTypeDefaultDescription
credential_refstringReference to secret element containing carrier API credentials (e.g. 46elks API username and password). Not needed when sandbox_managed is true.
sandbox_managedbooleanfalseWhen 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_numberstringPhone number in E.164 format (e.g. +46766861004)
elks_number_idstringInternal carrier number id set by provision — do not edit. Used by release / configure / test-connection ops to address the number at the carrier.
countrystringISO 3166-1 alpha-2 country code (e.g. SE, US, GB)
friendly_namestringHuman-readable label for this number
capabilitiesobjectEnabled capabilities for this number
default_sender_idstringDefault alphanumeric sender ID for outbound SMS (carrier-dependent, max 11 chars)
flash_smsbooleanfalseSend as flash SMS (displayed immediately, not stored) by default
caller_idstringCaller ID for outbound calls (defaults to phone_number)
voice_start_urlstringWebhook URL for voice call flow (IVR actions)
webrtc_sip_userstringSIP username for the 46elks WebRTC endpoint (see 46elks dashboard)
webrtc_sip_passstringSIP password for the 46elks WebRTC endpoint — stored encrypted
telephony_agentstringAgent 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"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_numberstringE.164 phone number to bridge inbound calls to when incoming_handling = forward_to (e.g. +46701234567).
carrierstring"46elks"Carrier provider identifier
carrier_api_urlstring""Carrier API base URL (defaults to provider standard)
max_retriesinteger3Maximum delivery retry attempts
retry_backoff_msinteger2000Base 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 /calls with voice_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 up hello.callid in call_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. Requires spec.phone_number + spec.credential_ref to 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 with telephony_agent set AND a live TelephonyManager registration (see sip-register-test for 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 message through 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 from field 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 a telephony_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 view main.py for 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, and intention are all independently optional. spec MUST 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 from update) 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 by update_element_meta storage 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

CodeClassRetryableDescription
PHONE_AUTH_FAILEDauthnoCarrier API authentication failed (invalid credentials)
PHONE_SEND_FAILEDinternalyesFailed to send SMS via carrier API
PHONE_INVALID_NUMBERvalidationnoRecipient phone number is not valid E.164
PHONE_REJECTEDinternalnoCarrier rejected the message (permanent failure)
PHONE_RATE_LIMITEDinternalyesCarrier rate limit exceeded
PHONE_NUMBER_NOT_PROVISIONEDvalidationnoPhone number is not provisioned or active with the carrier
PHONE_CREDENTIAL_MISSINGauthnocredential_ref not set or referenced secret not found
PHONE_CALL_FAILEDinternalyesFailed to initiate voice call
PHONE_INSUFFICIENT_BALANCEinternalnoCarrier 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