The Blog
Engineering6 min read

Give an agent a phone number in 60 seconds

A guided tour through the shortest path from an empty terminal to a ringing phone — define a connection, provision a number, attach the two, and place your first call. Four requests, no carrier paperwork.

Yoav ShaiFounder

The fastest way to understand Saperly is to give an agent a phone number and call it. No telephony account, no SIP trunk, no carrier paperwork — four small HTTP requests stand between an empty terminal and a line that rings. Let’s walk the whole path, then open the hood and look at what each request quietly set up for you.

Everything below hits one base URL, and every request is authenticated with a single bearer token. There is no separate provisioning console, no ticket queue, no “talk to sales” step. If you can curl, you can run a phone line.

The shape of the loop

Four resources, in order: a connection (the brain), a number (the line), the attachment that binds them, and a call. The first two are independent — you can create either one first — but the connection is the interesting one, so we’ll start there.

1. Grab an API key

Sign in to the dashboard and mint an API key — it’s shown once, so copy it now. New accounts start with free usage credit, which is more than enough to make a few calls and watch the whole thing work.

terminal
export SAPERLY_API_KEY="sk_live_…"
export API="https://api.saperly.com/v2"

2. Define a connection

A connection is the reusable handler: instructions and a voice. You define it once and attach it to as many numbers as you like — one connection, a whole fleet. Any opening line the agent should say goes in the instructions (the system prompt); there’s no separate greeting field. Creating one returns an id prefixed cn_; hold onto it.

create-connection.sh
CONNECTION_ID=$(curl -s "$API/connections" \
  -H "Authorization: Bearer $SAPERLY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Acme Support",
    "instructions": "You are a friendly support agent for Acme. Open with: Thanks for calling Acme — how can I help?",
    "complianceEnabled": true,
    "disclosure": "You are speaking with an AI assistant for Acme."
  }' | jq -r '.id')
# → cn_…

The disclosure isn’t decoration. With complianceEnabled on, it’s the line’s first, uninterruptible utterance — the TCPA notice spoken before the agent ever takes a turn, so the called party always learns an AI is on the line and the platform won’t let the call go out non-compliant. Leave disclosure blank with compliance on and Saperly fills a standard, org-named default for you. The connection is fully provisioned the moment this request returns; there’s no separate “publish” step.

3. Provision a number and attach the connection

Ask for a number — optionally pinned to an area code — and you get one back immediately, with its own id. Then point that number at your connection with a second request. Note the shape: the number id lives in the path, and only the connection id is in the body.

provision-and-attach.sh
NUMBER_ID=$(curl -s "$API/numbers" \
  -H "Authorization: Bearer $SAPERLY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"areaCode":"415"}' | jq -r '.id')

curl -s "$API/numbers/$NUMBER_ID/connection" \
  -H "Authorization: Bearer $SAPERLY_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"connectionId\":\"$CONNECTION_ID\"}"

That’s the binding done. The number now answers with your connection’s brain, disclosure, and voice. Because the connection is a separate, reusable resource, you can run this attach step against a hundred numbers and they’ll all behave identically — change the connection once and every line moves with it.

4. Place the call

Place an outbound call from your number to a phone you own, and pick up. You’re talking to the connection you configured two steps ago. The call originates from the number, so the payload names it fromNumberId.

call.sh
curl -s "$API/calls" \
  -H "Authorization: Bearer $SAPERLY_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"fromNumberId\":\"$NUMBER_ID\",\"to\":\"+14155550100\"}"

That’s the entire loop. The phone rings and the connection answers.

What actually happened

Four requests, but more than four things got set up. The connection registered an accountable identity for the line. The enforced disclosure means the person who picked up was told they were speaking to an AI — spoken first, before the agent could take a turn — and that notice was written as a compliance event, not left as a promise. Consent is checked before a call connects, and every call you place lands in an append-only audit trail you can query later. None of that was extra work; it’s the default, which is the whole point — you reach for a number and the trust machinery comes attached.

Where to go next

  • Attach the same connection to more numbers — it’s reusable, so a ten-thousand-number fleet shares one config and one place to change it.
  • Send and receive SMS on the same number with POST /v2/messages.
  • Tune the disclosure copy per jurisdiction so the AI-notice matches local law.
  • Query GET /v2/calls to pull the audit trail for everything the number has done.

By the time you ship, the compliance story is already done — you wrote four requests and got a working, accountable phone line out of it. The full reference lives in the docs; this was just the 60-second version.

Yoav ShaiFounder

Keep reading

One API call. Your agent has a phone number.

Identity, consent, disclosure, and an audit trail — built in. Start with $5 in free credit, no card required.