---
canonical_url: "https://docs.composio.dev/toolkits/saperly"
markdown_url: "https://docs.composio.dev/toolkits/saperly.md"
product: "platform"
version: null
last_updated: null
---

# Saperly

Saperly is the phone carrier for AI agents — phone numbers, voice, SMS, and compliance in one API.

- **Category:** phone & sms
- **Auth:** API_KEY
- **Composio-managed OAuth available?** N/A
- **Tools:** 30
- **Triggers:** 0
- **Slug:** `SAPERLY`
- **Version:** 00000000_00

## Tools

### Bind Connection to Number

**Slug:** `SAPERLY_ASSIGN_NUMBER_CONNECTION`

Attach a connection (the AI persona / answering brain from SAPERLY_CREATE_CONNECTION) to a phone number, so inbound calls and SMS to that number — and outbound calls placed from it — are handled by that connection. A number without a bound connection cannot answer calls (and PLACE_CALL fails with 422 NumberHasNoConnection), so this is the wiring step that makes a number answerable. Get a connection id from SAPERLY_LIST_CONNECTIONS or create one with SAPERLY_CREATE_CONNECTION. Returns the updated number object. Free.

#### Input Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `id` | string | Yes | The phone number's id (a UUID) to bind, as returned by SAPERLY_LIST_NUMBERS or SAPERLY_GET_NUMBER. |
| `connectionId` | string | Yes | The connection id (a UUID) to bind as this number's answering brain. A connection is an agent persona / call handler that decides how inbound calls and SMS to this number are handled. Obtain one from SAPERLY_LIST_CONNECTIONS or create a new one with SAPERLY_CREATE_CONNECTION. |
| `idempotency_key` | string | No | Optional idempotency key. Reusing the same key with an identical body safely replays the assignment instead of applying it twice. |

#### Output

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `data` | string | Yes | Data from the action execution |
| `error` | string | No | Error if any occurred during the execution of the action |
| `successful` | boolean | Yes | Whether or not the action execution was successful or not |

### Check Consent

**Slug:** `SAPERLY_CHECK_CONSENT`

Check whether a contact currently has active TCPA consent for one of your Saperly numbers, given numberId (your number) and peerNumber (the contact's E.164 number) as query params. Returns {hasConsent: boolean} and, when hasConsent is true, {type: '<consentType>'} (the 'type' key is ABSENT when hasConsent is false). Saperly REFUSES outbound SMS (SAPERLY_SEND_SMS) and voice calls (SAPERLY_PLACE_CALL) — both of which are paid, balance-metered — to a destination without active consent, so call this free, read-only check FIRST to decide whether you need SAPERLY_RECORD_CONSENT before spending on a paid send/call that would otherwise fail the compliance gate. No side effects.

#### Input Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `numberId` | string | Yes | Id of your Saperly phone number (a UUID, as returned by SAPERLY_LIST_NUMBERS) to check consent FROM. Required. |
| `peerNumber` | string | Yes | The contact's phone number in E.164 format to check consent FOR, e.g. +14155550123. The leading '+' is URL-encoded automatically. Required. |

#### Output

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `data` | string | Yes | Data from the action execution |
| `error` | string | No | Error if any occurred during the execution of the action |
| `successful` | boolean | Yes | Whether or not the action execution was successful or not |

### Create Connection

**Slug:** `SAPERLY_CREATE_CONNECTION`

Create a connection: the AI 'brain' that answers calls and (optionally) SMS on a Saperly phone number. A phone number binds to a connection via SAPERLY_ASSIGN_NUMBER_CONNECTION in order to answer calls. Only `name` is required. mode 'hosted' (default) = an in-network STT->LLM->TTS voice assistant driven by `instructions` (the system prompt), `tts` voice (voiceId from SAPERLY_LIST_VOICES), and `language`; mode 'manual' = your own webhook brain (set `manualWebhookUrl`). complianceEnabled defaults true — Saperly then speaks a TCPA `disclosure` as the first uninterruptible utterance, auto-filling a standard org-named disclosure if you leave it empty. Returns the created connection (201). Free. (The 'backend' field is intentionally not exposed: only 'network' works; 'openai_realtime' is server-rejected.)

#### Input Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `llm` | object | No | Model override for the hosted assistant. |
| `tts` | object | No | The hosted assistant's text-to-speech voice. |
| `mode` | string ("hosted" | "manual") | No | How the line is handled. 'hosted' (default): an in-network voice assistant (STT -> LLM -> TTS) is the brain, driven by `instructions`, `tts`, and `language`. 'manual': your own webhook brain is the brain (set `manualWebhookUrl`). |
| `name` | string | Yes | Human-readable connection name (1-120 chars). |
| `language` | string | No | Spoken language as a BCP-47 tag (e.g. 'en', 'es', 'en-US'), used as the transcription hint. See SAPERLY_LIST_LANGUAGES. Send '' to clear (auto-detect). |
| `disclosure` | string | No | TCPA notice spoken first (uninterruptibly) when `complianceEnabled` is on. Leave empty to let Saperly auto-fill a standard org-named default. |
| `mcpServers` | array | No | MCP servers the hosted assistant may call as tools during a conversation. Hosted mode only. |
| `callControl` | object | No | Which call-control actions the connection's brain may take. |
| `instructions` | string | No | System prompt defining the assistant's behavior and opening line (there is no separate greeting field). Hosted mode. |
| `smsAutoReply` | boolean | No | Hosted-line opt-in to auto-answer inbound SMS with the model. Defaults off. |
| `idempotency_key` | string | No | Optional idempotency key. Reusing the same key with an identical body safely replays the creation instead of creating a second connection. |
| `manualWebhookUrl` | string | No | HTTPS manual-mode webhook-brain URL where Saperly POSTs each turn. Only for mode='manual'. |
| `complianceEnabled` | boolean | No | When true (default), the do-not-message tool is exposed and `disclosure` is spoken first (an uninterruptible TCPA notice). |

#### Output

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `data` | string | Yes | Data from the action execution |
| `error` | string | No | Error if any occurred during the execution of the action |
| `successful` | boolean | Yes | Whether or not the action execution was successful or not |

### Delete Connection

**Slug:** `SAPERLY_DELETE_CONNECTION`

PERMANENTLY AND IRREVERSIBLY delete a connection (the AI answering brain / persona that handles calls and SMS) by its id. THIS CANNOT BE UNDONE — there is no restore. WARNING: any phone number currently bound to this connection will immediately STOP handling inbound calls and SMS (it has no answering brain) until you bind another connection to it. Before deleting, call SAPERLY_LIST_NUMBERS to check which numbers reference this connectionId, and rebind them with SAPERLY_ASSIGN_NUMBER_CONNECTION first. Returns { status: 'deleted' }. Free, but destructive.

#### Input Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `id` | string | Yes | The connection id (a UUID) to permanently delete, as returned by SAPERLY_LIST_CONNECTIONS or SAPERLY_CREATE_CONNECTION. IRREVERSIBLE: the connection is destroyed and cannot be recovered. |
| `idempotency_key` | string | No | Optional idempotency key for safe retries. Reusing the same key replays the same result instead of failing on an already-deleted connection. |

#### Output

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `data` | string | Yes | Data from the action execution |
| `error` | string | No | Error if any occurred during the execution of the action |
| `successful` | boolean | Yes | Whether or not the action execution was successful or not |

### End Call

**Slug:** `SAPERLY_END_CALL`

End an in-progress call by its id, hanging up and settling the metered cost. This is the safe, cheap, RECOMMENDED companion to SAPERLY_PLACE_CALL: placing a call starts per-minute billing, and this tool is how an agent stops that meter — call it as soon as you are done with the call. Ending is itself free (it ends spend rather than adding it). Returns the call object; durationSec and costCents may still be NULL if the call has not settled yet (null does NOT mean free — poll SAPERLY_GET_CALL afterwards for the final cost and duration). Only meaningful while a call is live; ending an already-ended call is a best-effort no-op.

#### Input Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `id` | string | Yes | The call id (a UUID) to end, as returned by SAPERLY_PLACE_CALL or SAPERLY_LIST_CALLS. Ending an in-progress call hangs it up and stops the per-minute billing. |
| `idempotency_key` | string | No | Optional idempotency key for safe retries. Reusing the same key replays the original end request rather than issuing a second one. |

#### Output

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `data` | string | Yes | Data from the action execution |
| `error` | string | No | Error if any occurred during the execution of the action |
| `successful` | boolean | Yes | Whether or not the action execution was successful or not |

### Get Call

**Slug:** `SAPERLY_GET_CALL`

Fetch a single call by its id, returning its status, per-minute rate, final duration (durationSec) and cost (costCents), and the hasRecording / hasTranscript flags. costCents and durationSec are NULL until the call settles (in progress / not yet billed) — null does NOT mean free, so this is the tool to poll after SAPERLY_PLACE_CALL to learn a call's final cost and duration. Check hasRecording / hasTranscript before calling SAPERLY_GET_CALL_RECORDING / SAPERLY_GET_CALL_TRANSCRIPT: those artefact endpoints 404 until the call has ended and been processed, and a 404 there means 'no artefact yet', not 'no such call'. Free, read-only.

#### Input Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `id` | string | Yes | The call id (a UUID) to fetch, as returned by SAPERLY_PLACE_CALL or SAPERLY_LIST_CALLS. |

#### Output

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `data` | string | Yes | Data from the action execution |
| `error` | string | No | Error if any occurred during the execution of the action |
| `successful` | boolean | Yes | Whether or not the action execution was successful or not |

### Get Call Recording

**Slug:** `SAPERLY_GET_CALL_RECORDING`

Get the audio recording for a call and return it as a downloadable file. The API answers with a 302 redirect to a signed download URL when a recording exists; the recording is fetched and returned as a file reference (under `content`). A recording exists only if the call's connection had recordingEnabled=true AND the call actually connected and completed — so for many calls there is simply no recording. The endpoint uses the same 404 response for an unknown call and a call without an artifact, so verify the id and check SAPERLY_GET_CALL.hasRecording=true before calling this. Free, read-only.

#### Input Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `id` | string | Yes | The call id (a UUID) whose audio recording to fetch, as returned by SAPERLY_PLACE_CALL or SAPERLY_LIST_CALLS. Call SAPERLY_GET_CALL first and ensure hasRecording=true — this endpoint returns 404 (meaning 'no recording available'), not an error, when the call has no recording. |

#### Output

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `data` | string | Yes | Data from the action execution |
| `error` | string | No | Error if any occurred during the execution of the action |
| `successful` | boolean | Yes | Whether or not the action execution was successful or not |

### Get Call Transcript

**Slug:** `SAPERLY_GET_CALL_TRANSCRIPT`

Fetch the transcript (conversation turns/segments) of a completed call by its id. Transcripts only exist AFTER a call connects, ends, and is processed. The endpoint uses the same 404 for an unknown call and a valid call whose transcript is not ready. Before calling this, poll SAPERLY_GET_CALL until the call has settled (durationSec / costCents are non-null) and hasTranscript is true. The returned payload is exposed under a 'transcript' key with call metadata and ordered role/text/timestamp turns. Free, read-only.

#### Input Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `id` | string | Yes | The call id (a UUID) whose transcript to fetch, as returned by SAPERLY_PLACE_CALL or SAPERLY_LIST_CALLS. Call SAPERLY_GET_CALL first and ensure hasTranscript=true — this endpoint 404s until the call has ended and been processed. |

#### Output

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `data` | string | Yes | Data from the action execution |
| `error` | string | No | Error if any occurred during the execution of the action |
| `successful` | boolean | Yes | Whether or not the action execution was successful or not |

### Get Connection

**Slug:** `SAPERLY_GET_CONNECTION`

Fetch a single connection (the AI answering brain / persona that handles calls and SMS) by its id, returning its full config: name, mode, backend, instructions, tts voice, language, compliance/disclosure settings, recordingEnabled, and manual webhook URL. Sensitive signing secrets are redacted. Use after SAPERLY_LIST_CONNECTIONS to inspect one connection. Free, read-only.

#### Input Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `id` | string | Yes | The connection id (a UUID) to fetch, as returned by SAPERLY_LIST_CONNECTIONS or SAPERLY_CREATE_CONNECTION. |

#### Output

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `data` | string | Yes | Data from the action execution |
| `error` | string | No | Error if any occurred during the execution of the action |
| `successful` | boolean | Yes | Whether or not the action execution was successful or not |

### Get Phone Number

**Slug:** `SAPERLY_GET_NUMBER`

Fetch a single phone number by its id, returning its E.164 phoneNumber, bound connectionId, webhookUrl, country, numberType, pricing and lifecycle fields. Use after SAPERLY_LIST_NUMBERS to inspect one number's current configuration. Free, read-only.

#### Input Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `id` | string | Yes | The phone number id (a UUID) to fetch, as returned by SAPERLY_LIST_NUMBERS. |

#### Output

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `data` | string | Yes | Data from the action execution |
| `error` | string | No | Error if any occurred during the execution of the action |
| `successful` | boolean | Yes | Whether or not the action execution was successful or not |

### Get Usage & Balance

**Slug:** `SAPERLY_GET_USAGE`

Return the workspace usage summary and prepaid balance: calls {count, totalCostCents, totalDurationSec}, messages {count}, and balanceCents (the prepaid balance, in cents). ALWAYS call this before any paid action to check balanceCents: SAPERLY_PROVISION_NUMBER (~$2/mo), SAPERLY_SEND_SMS (~2c/segment) and SAPERLY_PLACE_CALL (~26c/min) all draw down this single balance, and a depleted (0) balance causes them to fail. This is also the best whoami / key check: a 200 confirms the connected key is valid and which workspace it maps to (prefer it over /health for that). Pass the optional 'since' (ISO-8601) to scope the usage window. Free, read-only, no pagination.

#### Input Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `since` | string | No | Optional ISO-8601 timestamp lower bound (e.g. '2026-07-01T00:00:00Z'). Only usage at or after this instant is counted; omit for all-time totals. Does not affect balanceCents (always the current balance). |

#### Output

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `data` | string | Yes | Data from the action execution |
| `error` | string | No | Error if any occurred during the execution of the action |
| `successful` | boolean | Yes | Whether or not the action execution was successful or not |

### List Calls

**Slug:** `SAPERLY_LIST_CALLS`

Return the full voice-call history (inbound + outbound) for the connected Saperly workspace. Each call carries its id, numberId, direction, to and from_number (E.164), status, rateCentsPerMin, durationSec, costCents, hasRecording and hasTranscript. Note: durationSec and costCents are null until the call settles (null means 'not yet settled', not 'free'). Use the id with SAPERLY_GET_CALL for full detail, and check hasRecording / hasTranscript before calling SAPERLY_GET_CALL_RECORDING / SAPERLY_GET_CALL_TRANSCRIPT (they 404 when the artifact does not exist yet). Read-only and free. Returns a bare array — there is no pagination and no filter parameter.

#### Output

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `data` | string | Yes | Data from the action execution |
| `error` | string | No | Error if any occurred during the execution of the action |
| `successful` | boolean | Yes | Whether or not the action execution was successful or not |

### List Connections

**Slug:** `SAPERLY_LIST_CONNECTIONS`

Return every connection (the AI persona / answering 'brain' that handles calls and SMS) in the connected Saperly workspace. Each carries id, name, mode (hosted|manual), backend, instructions, llm, tts voice, language, mcpServers, callControl, complianceEnabled, smsAutoReply, disclosure and recordingEnabled. Use this to find a connectionId to bind to a number or use on a call. Read-only and free. Returns a bare array (no pagination).

#### Output

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `data` | string | Yes | Data from the action execution |
| `error` | string | No | Error if any occurred during the execution of the action |
| `successful` | boolean | Yes | Whether or not the action execution was successful or not |

### List Consent Records

**Slug:** `SAPERLY_LIST_CONSENT`

Return every TCPA consent record in the connected Saperly workspace, with each record's id, numberId (the Saperly number contact is authorized FROM), peerNumber (the contact's E.164 number), consentType (explicit_outbound or implied_inbound), source, grantedAt and revokedAt. Consent is Saperly's compliance gate: outbound SMS (SAPERLY_SEND_SMS) and calls (SAPERLY_PLACE_CALL) are only permitted to a destination that has an active consent record, and these records form an immutable TCPA audit trail. Use this to review or export the full consent log; to check a single contact instead, use SAPERLY_CHECK_CONSENT. Read-only and free. Returns a bare array (no pagination).

#### Output

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `data` | string | Yes | Data from the action execution |
| `error` | string | No | Error if any occurred during the execution of the action |
| `successful` | boolean | Yes | Whether or not the action execution was successful or not |

### List Languages

**Slug:** `SAPERLY_LIST_LANGUAGES`

Return the spoken languages Saperly supports for voice connections, each as a {code, name} pair (about 42 entries, e.g. {'code':'en','name':'English'}). A language `code` is what you pass as a connection's `language` field, and as the `language` filter to SAPERLY_LIST_VOICES (that filter does a PREFIX match, so 'fr' also matches regional variants such as 'fr-CA'). Note this base-code set is narrower than the /voices language tags, which include regional variants. Read-only and free. Returns a bare array (no pagination).

#### Output

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `data` | string | Yes | Data from the action execution |
| `error` | string | No | Error if any occurred during the execution of the action |
| `successful` | boolean | Yes | Whether or not the action execution was successful or not |

### List Messages

**Slug:** `SAPERLY_LIST_MESSAGES`

Return SMS messages in the connected Saperly workspace (both inbound and outbound). Each message has id, numberId, direction, to, from_number, body, segments, status and createdAt. Pass `numberId` to filter to a single phone number's messages; `numberId` is the ONLY filter available. With no filter this returns the FULL message history, which may be large — there is no pagination. Free and read-only; returns a bare array.

#### Input Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `numberId` | string | No | Optional filter: return only messages on this phone number id (a UUID, as returned by SAPERLY_LIST_NUMBERS). Omit to return every message in the workspace. This is the ONLY filter the API supports. |

#### Output

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `data` | string | Yes | Data from the action execution |
| `error` | string | No | Error if any occurred during the execution of the action |
| `successful` | boolean | Yes | Whether or not the action execution was successful or not |

### List Phone Numbers

**Slug:** `SAPERLY_LIST_NUMBERS`

Return every phone number provisioned in the connected Saperly workspace, with each number's id, phoneNumber (E.164), the connectionId (answering brain) bound to it, webhookUrl, country, numberType, monthlyPriceCents and lifecycle timestamps. Use this to discover a number's id before sending SMS, placing a call, or configuring the number. Read-only and free. Returns a bare array (no pagination).

#### Output

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `data` | string | Yes | Data from the action execution |
| `error` | string | No | Error if any occurred during the execution of the action |
| `successful` | boolean | Yes | Whether or not the action execution was successful or not |

### List Voices

**Slug:** `SAPERLY_LIST_VOICES`

List the text-to-speech voices available for Saperly connections. Each voice has an id, name, gender and language; a voice id is what you pass as tts.voiceId when creating or updating a connection (SAPERLY_CREATE_CONNECTION / SAPERLY_UPDATE_CONNECTION). ALWAYS pass `language` to filter — the unfiltered list is ~751 voices across 52 language tags, which is expensive to return in full. NOTE the filter is a PREFIX/family match, not exact: language='fr' returns both 'fr' and 'fr-CA'. Get language codes from SAPERLY_LIST_LANGUAGES. Read-only and free; returns a bare array (no pagination).

#### Input Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `language` | string | No | Optional language filter. PREFIX / family match, NOT exact: 'fr' returns both 'fr' and 'fr-CA'; 'en' returns 'en', 'en-US', 'en-GB', etc. Strongly recommended — the unfiltered list is ~751 voices. Uses the broader voice tag set (regional variants like en-US, fr-CA), which is wider than SAPERLY_LIST_LANGUAGES' base codes; get base codes there. |

#### Output

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `data` | string | Yes | Data from the action execution |
| `error` | string | No | Error if any occurred during the execution of the action |
| `successful` | boolean | Yes | Whether or not the action execution was successful or not |

### Place Call

**Slug:** `SAPERLY_PLACE_CALL`

PAID / METERED — places a REAL outbound voice call that RINGS A REAL PHONE and BILLS PER MINUTE (~26¢/min); it keeps billing until hung up — end it with SAPERLY_END_CALL to stop the meter. Calls FROM a Saperly number (`fromNumberId` from SAPERLY_LIST_NUMBERS) TO an E.164 destination (`to`, e.g. +14155550123). The connection bound to the originating number answers; a number with NO bound connection cannot hold a conversation (bind one with SAPERLY_ASSIGN_NUMBER_CONNECTION). Optionally override the brain for this call with `connectionId` and/or a per-call `instructions` prompt. CONSENT GATE (TCPA): the destination MUST have active consent — call SAPERLY_RECORD_CONSENT first and SAPERLY_CHECK_CONSENT to verify, or it is rejected (403). Returns the call (201) with id + status; `durationSec`/`costCents` are NULL until the call settles (ends) — null does NOT mean it was free. Pass `idempotency_key` on retries to avoid double-dialing. May fail 402 (funds) or 422 (no connection).

#### Input Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `to` | string | Yes | Destination phone number to dial, in E.164 format (e.g. +14155550123). The destination MUST have an active consent record (TCPA gate): record it first with SAPERLY_RECORD_CONSENT and confirm with SAPERLY_CHECK_CONSENT, or the call is rejected (403 RecipientOptedOut). |
| `connectionId` | string | No | Optional connection id (a UUID) to use as the answering brain for THIS call, overriding the connection bound to the originating number. Omit to use the number's bound connection. Get one from SAPERLY_LIST_CONNECTIONS. |
| `fromNumberId` | string | Yes | Id (a UUID) of the Saperly number to place the call FROM, as returned by SAPERLY_LIST_NUMBERS. This number MUST have a bound connection (the answering brain) or the call cannot hold a conversation — bind one first with SAPERLY_ASSIGN_NUMBER_CONNECTION. |
| `instructions` | string | No | Optional per-call system prompt that overrides the connection's saved instructions for this call only. Omit to use the connection's defaults. |
| `idempotency_key` | string | No | Optional idempotency key. STRONGLY recommended on retries: reusing the same key with an identical body safely replays the original call instead of placing (and billing) a second one. |

#### Output

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `data` | string | Yes | Data from the action execution |
| `error` | string | No | Error if any occurred during the execution of the action |
| `successful` | boolean | Yes | Whether or not the action execution was successful or not |

### Provision Phone Number

**Slug:** `SAPERLY_PROVISION_NUMBER`

PAID / METERED: provision (buy) a new phone number in a country. This SPENDS MONEY — it charges the workspace prepaid balance an upfront fee plus a RECURRING MONTHLY rent (US local ~ $1.79/mo, varies by country/type). Before calling this: (1) call SAPERLY_QUOTE_NUMBER_PRICE to get the live price and pass it as expectedMonthlyPriceCents / expectedUpfrontPriceCents to guard against price drift (a higher live price fails with 409 PriceChanged unless approveHigherPrice=true); (2) confirm sufficient funds by checking balanceCents via SAPERLY_GET_USAGE. Defaults country='US', numberType='local'. Pass an idempotency_key to avoid double-provisioning on retry. Fails 402 (InsufficientFunds / PaymentMethodRequired), 403 (CountryNotAvailable), 404 (NoNumbersAvailable), 409 (NumberQuotaExceeded / PriceChanged).

#### Input Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `country` | string | No | ISO 3166-1 alpha-2 country code to provision a number in (e.g. US, GB). Defaults to US. |
| `areaCode` | string | No | Preferred area code / city prefix to search within the country (e.g. 415). Omit to take any available number. |
| `numberType` | string ("local" | "toll_free" | "mobile" | "national") | No | Kind of number to provision: one of 'local', 'toll_free', 'mobile', 'national'. Defaults to 'local'. |
| `idempotency_key` | string | No | Optional idempotency key. The same key plus the same body replays the same result instead of provisioning again — pass it on retries to prevent double-charging. |
| `approveHigherPrice` | boolean | No | Set true to accept a live order price higher than the expected* values you quoted, instead of failing with 409 PriceChanged. |
| `expectedMonthlyPriceCents` | integer | No | The monthly price (in cents) you expect, taken from SAPERLY_QUOTE_NUMBER_PRICE. If the live order price exceeds it, provision fails with 409 PriceChanged instead of charging — resubmit with approveHigherPrice=true to accept the higher price. |
| `expectedUpfrontPriceCents` | integer | No | The one-time upfront price (in cents) you expect, from SAPERLY_QUOTE_NUMBER_PRICE. Same PriceChanged guard as expectedMonthlyPriceCents. |

#### Output

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `data` | string | Yes | Data from the action execution |
| `error` | string | No | Error if any occurred during the execution of the action |
| `successful` | boolean | Yes | Whether or not the action execution was successful or not |

### Quote Number Price

**Slug:** `SAPERLY_QUOTE_NUMBER_PRICE`

READ-ONLY and FREE: quote the price to provision a phone number in a country/type, returning { customerMonthlyCents, customerUpfrontCents }. This DOES NOT provision, reserve, or charge anything — despite the name it has NO side effects. This is exactly the tool to call BEFORE the expensive SAPERLY_PROVISION_NUMBER (which spends ~$2/mo): (1) use it to size the cost and feed customerMonthlyCents / customerUpfrontCents straight into SAPERLY_PROVISION_NUMBER's expectedMonthlyPriceCents / expectedUpfrontPriceCents price-drift guards (the safe provisioning pattern); (2) compare against balanceCents from SAPERLY_GET_USAGE to confirm sufficient funds. Both country and numberType are required. Live-verified: US/local quoted 179 cents monthly and 179 cents upfront.

#### Input Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `country` | string | Yes | Required ISO 3166-1 alpha-2 country code to price a number in (e.g. US, GB). Mirrors SAPERLY_PROVISION_NUMBER's country input. |
| `numberType` | string ("local" | "toll_free" | "mobile" | "national") | Yes | Required kind of number to price: one of 'local', 'toll_free', 'mobile', 'national'. Mirrors SAPERLY_PROVISION_NUMBER's numberType input. |

#### Output

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `data` | string | Yes | Data from the action execution |
| `error` | string | No | Error if any occurred during the execution of the action |
| `successful` | boolean | Yes | Whether or not the action execution was successful or not |

### Record Consent

**Slug:** `SAPERLY_RECORD_CONSENT`

Record TCPA consent for a contact so outbound SMS (SAPERLY_SEND_SMS) and calls (SAPERLY_PLACE_CALL) from your number are permitted. Saperly's compliance gate: for a cold contact call this BEFORE sending; confirm with SAPERLY_CHECK_CONSENT, revoke with SAPERLY_REVOKE_CONSENT. LEGAL ARTEFACT — DO NOT FABRICATE CONSENT: it writes to an IMMUTABLE audit trail asserting a real human gave consent to be contacted. Only record consent ACTUALLY obtained (an opt-in the contact gave, or an inbound message/call they initiated). Recording consent not truly obtained is a false compliance record and a legal violation; never do it merely to unblock a send. Requires numberId (your number, from SAPERLY_LIST_NUMBERS), peerNumber (the contact's E.164 number), consentType ('explicit_outbound' = opted in, or 'implied_inbound' = they contacted you first — legally distinct), and source (a note on how/where consent was obtained). Returns 201 with the record. Free.

#### Input Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `source` | string | Yes | Free-text audit note recording HOW and WHERE this consent was obtained (the legal provenance written to the immutable compliance trail), e.g. 'web signup form checkbox 2026-07-08' or 'inbound SMS reply START'. Describe the real event — do not invent it. |
| `numberId` | string | Yes | Id of YOUR Saperly number the consent applies to — outbound contact from this number to the contact will be authorized (from SAPERLY_LIST_NUMBERS). |
| `peerNumber` | string | Yes | The contact's phone number in E.164 format (leading '+', full international number) that consented to be contacted, e.g. +14155550123. |
| `consentType` | string ("implied_inbound" | "explicit_outbound") | Yes | How consent was ACTUALLY obtained — these are legally distinct, pick the true one: 'explicit_outbound' (the contact affirmatively opted in / gave express written consent to receive your outbound messages or calls) or 'implied_inbound' (the contact initiated contact by messaging or calling you first, implying consent to reply). |
| `idempotency_key` | string | No | Optional idempotency key. The same key plus an identical body replays the same result instead of creating a duplicate — pass it on retries. |

#### Output

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `data` | string | Yes | Data from the action execution |
| `error` | string | No | Error if any occurred during the execution of the action |
| `successful` | boolean | Yes | Whether or not the action execution was successful or not |

### Release Phone Number

**Slug:** `SAPERLY_RELEASE_NUMBER`

PERMANENTLY AND IRREVERSIBLY release a phone number back to the carrier. THIS CANNOT BE UNDONE: the number leaves the workspace, is returned to the carrier pool, and CANNOT be recovered or reclaimed. Inbound calls and SMS to it STOP IMMEDIATELY, and its monthly rent stops (releasedAt is set). Only call this to permanently decommission a number you are certain you no longer want — there is no undo. Free, but destructive.

#### Input Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `id` | string | Yes | The phone number id (a UUID) to release, as returned by SAPERLY_LIST_NUMBERS. IRREVERSIBLE: this number is returned to the carrier pool and cannot be reclaimed. |
| `idempotency_key` | string | No | Optional idempotency key for safe retries. Reusing the same key replays the same result instead of releasing twice. |

#### Output

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `data` | string | Yes | Data from the action execution |
| `error` | string | No | Error if any occurred during the execution of the action |
| `successful` | boolean | Yes | Whether or not the action execution was successful or not |

### Revoke Consent

**Slug:** `SAPERLY_REVOKE_CONSENT`

Revoke a contact's TCPA consent for a given Saperly number, blocking further outbound contact to them. CALL THIS WHEN A RECIPIENT SAYS 'STOP' / 'UNSUBSCRIBE' / 'do not contact me' — honoring an opt-out is a legal compliance obligation, and this is the primary legitimate use. OBSERVABLE EFFECT: once revoked, SAPERLY_SEND_SMS and SAPERLY_PLACE_CALL to this contact from this number are REFUSED (403 RecipientOptedOut) until consent is re-recorded with SAPERLY_RECORD_CONSENT. Consent records are an immutable audit trail: this does NOT delete the record — it marks it revoked by stamping a revokedAt timestamp, preserving the compliance history. Identify the contact by numberId (your number, from SAPERLY_LIST_NUMBERS) and peerNumber (the contact's E.164 number); no consentType is required. Pass idempotency_key to make retries safe. Returns { status: 'revoked' }. Free.

#### Input Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `numberId` | string | Yes | Id of YOUR Saperly phone number the consent applies to (from SAPERLY_LIST_NUMBERS). Consent is scoped to a (your number, contact) pair, so this identifies which number's authorization to revoke. |
| `peerNumber` | string | Yes | The CONTACT's phone number in E.164 format (leading '+', full international number) whose consent to revoke, e.g. +14155550123. No consentType is needed — any active consent for this pair is revoked. |
| `idempotency_key` | string | No | Optional idempotency key for safe retries. Reusing the same key replays the same result instead of revoking again. |

#### Output

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `data` | string | Yes | Data from the action execution |
| `error` | string | No | Error if any occurred during the execution of the action |
| `successful` | boolean | Yes | Whether or not the action execution was successful or not |

### Send SMS

**Slug:** `SAPERLY_SEND_SMS`

PAID / METERED: send a single SMS from one of your Saperly numbers to an E.164 destination. This SPENDS REAL MONEY and sends a REAL text message to a REAL phone (~2 cents per 160-character segment; a long body is split into multiple segments, each billed). TCPA CONSENT GATE (required): the API refuses to send unless the recipient has an active consent record (or messaged you within the last 24h). For cold outbound you MUST first call SAPERLY_RECORD_CONSENT for the destination, and you can verify it with SAPERLY_CHECK_CONSENT — otherwise this fails with 403 RecipientOptedOut / compliance. Before sending, confirm sufficient funds via SAPERLY_GET_USAGE (balanceCents). Pass idempotency_key to prevent a double-send on retry. Returns 201 with the queued message (id, status, segments). NOTE the response echoes the sending number as 'numberId' and 'from_number' (not 'fromNumberId'). Fails 402 (InsufficientFunds / SpendLimitExceeded), 400 (DestinationNotSupported / invalid request).

#### Input Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `to` | string | Yes | Recipient phone number in E.164 format (leading '+', full international number), e.g. +14155550123. |
| `body` | string | Yes | The message text (1-1600 characters). Long messages are split into multiple 160-character segments and BILLED PER SEGMENT (~2c each). |
| `fromNumberId` | string | Yes | Id of the Saperly number to send FROM (from SAPERLY_LIST_NUMBERS). Must be a number owned by the connected workspace and within the key's number allow-list. NOTE: the API echoes this back as 'numberId' in the response. |
| `idempotency_key` | string | No | Optional idempotency key. The same key plus the same body replays the same result instead of sending again — pass it on retries to prevent double-sending and double-charging. |

#### Output

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `data` | string | Yes | Data from the action execution |
| `error` | string | No | Error if any occurred during the execution of the action |
| `successful` | boolean | Yes | Whether or not the action execution was successful or not |

### Set Number Caller ID Name

**Slug:** `SAPERLY_SET_NUMBER_CALLER_ID`

Set the outbound caller ID name / CNAM (1-15 characters: letters, digits, spaces) presented to recipients on outbound calls from a Saperly phone number, or pass callerIdName=null to CLEAR it. callerIdName is required — supply a name to set it, or null to unset it. Returns the updated number object. Note: CNAM display depends on the destination carrier and does not propagate instantly, so recipients may not see the change immediately. Free.

#### Input Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `id` | string | Yes | The phone number's id (a UUID) whose outbound caller ID name to set, as returned by SAPERLY_LIST_NUMBERS or SAPERLY_GET_NUMBER. |
| `callerIdName` | string | Yes | The outbound caller ID name (CNAM) to display to call recipients — 1-15 characters, letters/digits/spaces only. Pass null to CLEAR a previously set caller ID. This field is required: supply a name to set it, or null to unset it. Note: CNAM propagation is carrier-dependent and not instantaneous, so recipients may not see the change immediately. |
| `idempotency_key` | string | No | Optional idempotency key for safe retries. Reusing the same key with an identical body replays the original result; reusing it with a different body is rejected (422). |

#### Output

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `data` | string | Yes | Data from the action execution |
| `error` | string | No | Error if any occurred during the execution of the action |
| `successful` | boolean | Yes | Whether or not the action execution was successful or not |

### Set Number SMS Sender ID

**Slug:** `SAPERLY_SET_NUMBER_SMS_SENDER`

Set the alphanumeric SMS sender id (1-11 alphanumeric chars, e.g. a brand name) shown as the 'from' on outbound SMS from this number, or pass smsSenderId=null to CLEAR it. Returns the updated number object. NOTE: sender-id support and display vary by destination country/carrier (the US and Canada notably restrict alphanumeric sender ids). Free.

#### Input Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `id` | string | Yes | The phone number id (a UUID) whose SMS sender id to set, as returned by SAPERLY_LIST_NUMBERS. |
| `smsSenderId` | string | Yes | The alphanumeric SMS sender id (1-11 alphanumeric characters, e.g. a brand name like 'ACME') shown as the 'from' on outbound SMS from this number. Pass null to CLEAR the sender id and fall back to the numeric caller. This is a required key — provide a string to set it or null to unset it. Note: alphanumeric sender ids are not supported by every destination country/carrier (the US and Canada notably restrict them and may ignore or reject the value); support and display vary by destination. |
| `idempotency_key` | string | No | Optional idempotency key for safe retries. Reusing the same key with an identical body replays the original result; reusing it with a different body is rejected. |

#### Output

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `data` | string | Yes | Data from the action execution |
| `error` | string | No | Error if any occurred during the execution of the action |
| `successful` | boolean | Yes | Whether or not the action execution was successful or not |

### Set Number Webhook

**Slug:** `SAPERLY_SET_NUMBER_WEBHOOK`

Set the HTTPS webhook URL that Saperly POSTs this number's events (inbound SMS/calls, status) to. The url is required and must be https://. Returns the updated number object. NOTE: this endpoint has no way to clear the URL back to null — once set it can only be changed to another URL, so set it deliberately. Free.

#### Input Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `id` | string | Yes | The phone number id (a UUID) whose event webhook to set, as returned by SAPERLY_LIST_NUMBERS. |
| `url` | string | Yes | The HTTPS webhook URL that Saperly will POST this number's events (inbound SMS/calls, status updates) to. Must start with 'https://'. Note: this endpoint has no way to clear the URL back to null once set — it can only be changed to another URL, so set it deliberately. |
| `idempotency_key` | string | No | Optional idempotency key for safe retries. Reusing the same key with an identical body replays the original result; reusing it with a different body is rejected. |

#### Output

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `data` | string | Yes | Data from the action execution |
| `error` | string | No | Error if any occurred during the execution of the action |
| `successful` | boolean | Yes | Whether or not the action execution was successful or not |

### Transfer Call

**Slug:** `SAPERLY_TRANSFER_CALL`

PAID / METERED — BLIND-transfers a LIVE, in-progress voice call to another destination `to` (an E.164 number like +15551230000, or a `sip:` URI). BLIND means the call is handed off UNCONDITIONALLY: there is NO confirmation from the transferee and the AI agent LOSES CONTROL of the call once transferred — treat it as a one-way, irrecoverable handoff. Only meaningful on a call that is actually connected/in-progress (identify it via SAPERLY_PLACE_CALL or SAPERLY_LIST_CALLS). The transferred leg KEEPS BILLING per minute, so this is not free — the meter runs on the new leg until the call ends. Returns the call object with its updated status; `durationSec` / `costCents` stay NULL until the call settles (null does NOT mean it was free). Pass `idempotency_key` on retries to avoid starting a second transfer leg. Fails 502 CallStartFailed if the transfer leg cannot be started, and 404 if the call id is unknown.

#### Input Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `id` | string | Yes | The id (a UUID) of the LIVE, in-progress call to blind-transfer, as returned by SAPERLY_PLACE_CALL or SAPERLY_LIST_CALLS. The call must be currently connected/in-progress for a transfer to be meaningful. |
| `to` | string | Yes | Where to blind-transfer the live leg — an E.164 phone number (e.g. +15551230000) OR a `sip:` URI (e.g. sip:agent@example.com). The call is handed off UNCONDITIONALLY with no confirmation from this destination, and the AI agent loses control of the call once transferred. |
| `idempotency_key` | string | No | Optional idempotency key. STRONGLY recommended on retries: reusing the same key with an identical body safely replays the original transfer instead of starting (and billing) a second transfer leg. |

#### Output

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `data` | string | Yes | Data from the action execution |
| `error` | string | No | Error if any occurred during the execution of the action |
| `successful` | boolean | Yes | Whether or not the action execution was successful or not |

### Update Connection

**Slug:** `SAPERLY_UPDATE_CONNECTION`

Partially update a connection (the AI answering brain bound to a number). Send only the fields you want to change (name, mode, instructions, llm, tts, language, mcpServers, callControl, complianceEnabled, smsAutoReply, disclosure, manualWebhookUrl); every unset field is left untouched. Send mcpServers=[] to clear all tools, or manualWebhookUrl="" to clear it. Writing RE-SYNCS the carrier, so an update has side effects beyond the stored record. Returns 200 with the full updated connection. Free.

#### Input Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `id` | string | Yes | The connection id (uuid) to update. |
| `llm` | object | No | New model override for the hosted assistant. Omit to keep the managed default. Hosted mode only. |
| `tts` | object | No | New TTS voice for the hosted assistant. Hosted mode only. |
| `mode` | string ("hosted" | "manual") | No | Change handling mode. 'hosted': an in-network voice assistant is the brain. 'manual': your own LLM is the brain. |
| `name` | string | No | New name (1–120 chars). |
| `language` | string | No | New spoken language BCP-47 tag. |
| `disclosure` | string | No | New TCPA notice spoken first, uninterruptibly, when compliance is on. |
| `mcpServers` | array | No | MCP tool servers the assistant may call. Send [] to clear all tools. |
| `callControl` | object | No | Which call-control actions the brain may take (end call, DTMF, transfer). Omitted sub-fields fall back to defaults. |
| `instructions` | string | No | New system prompt that defines the hosted assistant's behavior. |
| `smsAutoReply` | boolean | No | Hosted-line opt-in to auto-answer inbound SMS with the model. Default off. |
| `idempotency_key` | string | No | Optional idempotency key. The same key + an identical body replays the same result; the same key + a different body is 422-rejected. |
| `manualWebhookUrl` | string | No | HTTPS manual-mode webhook-brain URL. Only meaningful for manual mode; send "" to clear it. |
| `complianceEnabled` | boolean | No | Toggle the do-not-message tool + the TCPA `disclosure` opener. |

#### Output

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `data` | string | Yes | Data from the action execution |
| `error` | string | No | Error if any occurred during the execution of the action |
| `successful` | boolean | Yes | Whether or not the action execution was successful or not |
