Hosted Mode

Hosted mode is the easiest way to ship something useful.

You provide the prompt. Saperly handles the call loop.

Hosted mode costs 0.26/min,billedpersecondforZoneA(US/Canada).A3minutecall=0.26/min, billed per second for Zone A (US/Canada). A 3-minute call = 0.78. The line itself costs $2.50 per 30-day period (first number free for 30 days). International destinations use Zone B (×2) and Zone C (×3) — see Voice zones.
Compliance is opt-in. Saperly does NOT verify consent or play AI disclosures unless you set compliance_enabled: true on the line. With the flag off, TCPA, GDPR, and local telecom compliance are the customer’s responsibility. See Compliance and Consent for the full liability framing and how to opt in.

When to use it

Use hosted mode if:

  • you want to launch quickly
  • your call logic is mostly prompt-driven
  • you do not want to run a webhook server yet
  • you want a receptionist, intake agent, reminder bot, or FAQ line

Create a hosted line

$curl -X POST https://saperly.com/api/v1/lines \
> -H "Authorization: Bearer sk_live_..." \
> -H "Content-Type: application/json" \
> -d '{
> "name": "Receptionist",
> "mode": "hosted",
> "system_prompt": "You are a friendly receptionist for Acme Corp. Answer questions about hours, location, pricing, and appointment availability. Keep answers concise.",
> "begin_message": "Thanks for calling Acme Corp. How can I help?",
> "voice": "aria",
> "context_limit": 20
> }'

Important fields

system_prompt

This is the core behavior contract for the line.

Be specific. Tell the agent:

  • who it is
  • what it can help with
  • what it must never do
  • when it should escalate or stop

begin_message

Use this to avoid dead air at the start of the call.

If you leave it out, the line waits for the caller to speak first.

voice

Use GET /api/v1/voices to list available voice slugs before you pick one.

context_limit

Higher values preserve more conversation history but use more tokens and may slow longer calls.

Prompt engineering examples

The quality of your system_prompt (or topic for one-off calls) decides how the line behaves. Two concrete patterns to start from:

Receptionist

1{
2 "system_prompt": "You are the front desk at Acme Dental. Office hours are Mon-Fri 8am-5pm. You can schedule, reschedule, or cancel appointments. If the caller has a dental emergency, tell them to call 911 or go to the nearest ER. Never provide medical advice."
3}

Appointment reminder (one-off conversation call)

1{
2 "topic": "Call the patient to confirm their cleaning appointment tomorrow at 2pm. If they want to reschedule, offer the next three available slots: Wednesday 10am, Thursday 3pm, Friday 9am."
3}
Scope the prompt tightly. A short, specific prompt outperforms a long, open-ended one.

One-off conversation calls

If the prompt changes per call, use the conversation call endpoint instead of creating a new line every time.

$curl -X POST https://saperly.com/api/v1/calls/conversation \
> -H "Authorization: Bearer sk_live_..." \
> -H "Content-Type: application/json" \
> -d '{
> "line_id": "LINE_ID",
> "to_number": "+14155551234",
> "topic": "Call the customer to confirm their appointment tomorrow at 2pm and offer rescheduling if needed.",
> "begin_message": "Hi, this is Acme calling to confirm your appointment tomorrow at 2pm."
> }'

Good first hosted-mode use cases

  • front desk and receptionist
  • FAQ line
  • appointment reminders
  • after-hours triage
  • simple order status assistant

When not to use hosted mode

Do not start here if your call needs:

  • deep internal business logic
  • privileged backend actions
  • strict deterministic flow control
  • your own model runtime

In those cases, use Webhook Mode or Audio Mode.

Testing your hosted line

Once the line is created, walk through the full loop end to end before you put it in front of real callers.

1

Call your number

Dial the provisioned number returned by the create call. Any mobile phone works.

2

Verify the greeting

You should hear begin_message spoken aloud in the voice you selected. If there is silence, the line is likely waiting for you to speak first because begin_message was not set.

3

Have a short conversation

Ask 2-3 questions your system_prompt covers. Listen for on-topic answers and for the refusal behavior you defined (for example, the emergency handoff in the dental example).

4

Check the transcript

After you hang up, fetch the call to confirm Saperly captured it:

$curl https://saperly.com/api/v1/calls/CALL_ID \
> -H "Authorization: Bearer sk_live_..."

The response includes the full transcript.

5

Check billing

Confirm the charge landed on your account:

$curl https://saperly.com/api/v1/billing/balance \
> -H "Authorization: Bearer sk_live_..."

A 1-minute test call should reduce your balance by 26 cents.

If the AI does not respond after connecting, check: (1) your system_prompt is not empty, (2) the voice slug is valid (list with GET /api/v1/voices), (3) your balance is sufficient.

Call recording

Enable recording per line:

$curl -X POST https://saperly.com/api/v1/lines \
> -H "Authorization: Bearer sk_live_..." \
> -H "Content-Type: application/json" \
> -d '{
> "name": "Recorded line",
> "mode": "hosted",
> "system_prompt": "You are a helpful assistant.",
> "recording_enabled": true
> }'

Retrieve the recording URL from the call object after the call ends:

$curl https://saperly.com/api/v1/calls/CALL_ID \
> -H "Authorization: Bearer sk_live_..."

The response includes recording_url and transcript fields when recording is enabled.

Receiving call events on your webhook

Hosted-mode lines deliver two call lifecycle events:

  • call_incoming fires when an inbound call arrives, before the agent connects. Delivered to status_callback_url.
  • call_ended fires after the call ends and includes the full transcript in context. Delivered to the first URL set on your line, in this order:
    1. webhook_url (per-line, explicit)
    2. Account default_webhook_url (account-level fallback)
    3. status_callback_url (per-line, used as fallback)

To receive both events at one endpoint, set status_callback_url. To send call_ended to a different endpoint than call_incoming, also set webhook_url or account default_webhook_url.

Inbound SMS

Hosted mode handles voice for you. Voice mode does not gate SMS.

Every line — hosted, webhook, or audio — receives inbound SMS. Saperly records the consent record and logs a sms_received compliance event regardless of voice mode.

To auto-reply, set webhookUrl on the line. Your server receives the sms_received event and replies by calling POST /api/v1/messages with text generated by your own LLM:

$curl -X POST https://saperly.com/api/v1/lines \
> -H "Authorization: Bearer sk_live_..." \
> -H "Content-Type: application/json" \
> -d '{
> "name": "Receptionist",
> "mode": "hosted",
> "system_prompt": "...",
> "webhook_url": "https://your-app.example.com/saperly/sms"
> }'

A hosted line without webhookUrl still records consent and the compliance event on inbound SMS, but Saperly will not deliver the event to a customer endpoint and your code has nothing to reply with. See SMS and Conversations for the reply API and 24-hour conversation window.

End-to-end walkthrough

The fastest path from zero to a working hosted line. You provision one line, take an inbound call, and confirm the response path works.

1. Create a line

$curl -X POST https://saperly.com/api/v1/lines \
> -H "Authorization: Bearer sk_live_..." \
> -H "Content-Type: application/json" \
> -d '{
> "name": "Front desk",
> "mode": "hosted",
> "system_prompt": "You are a friendly receptionist for Acme Corp. Answer questions about hours, location, and pricing. Keep responses short.",
> "begin_message": "Thanks for calling Acme Corp. How can I help?",
> "voice": "aria"
> }'

2. Save the returned phone number and line ID

You’ll use the phone number to place a real test call. Save the line ID for later inspection.

3. Call the number

Verify:

  • the call connects
  • the AI answers
  • the opening message plays
  • the transcript appears on the call record
If the call does not connect, check: (1) your key environment matches the line’s environment (an sk_test_ key cannot reach a line provisioned with an sk_live_ key, and vice versa), (2) your balance is above 0, (3) the line status is active. Run GET /api/v1/lines to verify.

4. Inspect the created line

$curl https://saperly.com/api/v1/lines \
> -H "Authorization: Bearer sk_live_..."

5. Inspect the resulting call

$curl https://saperly.com/api/v1/calls \
> -H "Authorization: Bearer sk_live_..."

If this worked, you already have a production-shaped happy path.

Outbound calls

If the line has compliance_enabled: true, outbound calls require consent first. Compliance is off by default. See Compliance and Consent to decide whether to turn it on.

$curl -X POST https://saperly.com/api/v1/consent \
> -H "Authorization: Bearer sk_live_..." \
> -H "Content-Type: application/json" \
> -d '{
> "line_id": "LINE_ID",
> "phone_number": "+14155551234",
> "consent_type": "explicit_outbound",
> "source": "signup_form"
> }'

2. Place the call

$curl -X POST https://saperly.com/api/v1/calls \
> -H "Authorization: Bearer sk_live_..." \
> -H "Content-Type: application/json" \
> -d '{
> "line_id": "LINE_ID",
> "to_number": "+14155551234"
> }'

What to verify before you keep building

1

Line provisioned

GET /api/v1/lines returns your line with status active.

2

Inbound call works

Call your number, hear the AI answer.

3

Transcript populated

GET /api/v1/calls/{id} shows a transcript array.

4

Balance healthy

GET /api/v1/billing/balance shows the expected balance.

6

Outbound call works

POST /api/v1/calls with a consented number succeeds.

Common first-day mistakes

  • Using the wrong host. Use https://saperly.com/api/v1.
  • Forgetting consent before outbound calling when compliance_enabled: true.
  • Mixing up hosted-mode and webhook-mode fields on the same line.