Your agent calls from your number
Any AI agent can place calls and send texts from your own AI 2nd Line line. Not a generic robocall number — your registered, carrier-trusted number, the one people already recognise. You hand an agent a key, and you can take it back.
Why this is different from calling a telephony API
Anyone can wire up a raw telephony API in an afternoon and get a phone number. The hard part is everything carriers require before that number is trusted: brand registration, campaign approval, caller ID, and a reputation that doesn't get the call flagged as spam.
Your AI 2nd Line number already has all of that. This API lets an agent borrow it, under your permission, and lets you revoke that permission in one move. Texts an agent sends pass the same carrier compliance checks as texts you send yourself — and they land in the same thread you already read in the app.
Authentication
Every request carries your API key:
X-API-Key: a2l_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxx
Authorization: Bearer <key> works too, for agent frameworks that only speak
that header.
The key is shown once. We store only a hash of it, so it can never be displayed again — and a breach of our database cannot be replayed against the phone network. Lost it? Generate a new one, which revokes the old.
Getting a key
In the app: Settings › Your Agent › Agent API key › Generate a key. Copy it on the spot — that screen is the only place it is ever shown. The same screen tells you whether anything has used the key yet, and revokes it in one tap.
Your key, your traffic. Calls and texts sent with your key go out on our carrier registration under your number's identity, and you are responsible for all of them — including ones sent by software you did not write. Read the Acceptable Use Policy before you point an agent at this: consent before messaging, immediate opt-out handling, and no onward sending for third parties. Minting a key requires accepting the Terms of Service in the app, and we revoke keys that put the registration at risk.
Key management uses your normal app login, never the key itself. That is deliberate: a leaked key must not be able to mint its own replacement and turn into permanent access.
| Method | Path | Does |
|---|---|---|
GET | /agent-key | Whether a key exists, when it was made, when it was last used. Never returns the key. |
POST | /agent-key | Create a key, replacing any existing one. The only time the key is returned. |
DELETE | /agent-key | Revoke it. Takes effect immediately. |
Calls
GET /agent/numbers
Which of your lines this key may act on. Call it first rather than guessing.
{ "numbers": [ { "numberId": "10", "e164": "+14709120899", "label": "Business" } ] }
POST /agent/calls/outbound
{
"numberId": "10",
"to": "+14155551234",
"objective": "Ask whether they're open Saturday and what time they close."
}
objective is required. It is what the AI works
toward and how it knows the call is finished — nobody is on the line to hang up, so without a
goal a call has no reason to end. It is also the record of what your agent phoned someone about.
On a call with an objective the assistant introduces itself as an AI calling on your behalf, works the objective, reports only what it actually heard, and hangs up when it's done or when it's clear the answer isn't coming.
201 { "room": "rm_...", "status": "originating", "mode": "autonomous",
"from": "+14709120899", "to": "+14155551234" }
GET /agent/calls/:room
Status and transcript. Only finalised speech is returned — live transcription revises itself mid-sentence, and an agent acting on interim text acts on words nobody said.
{ "status": "live", "objective": "Ask whether they're open Saturday",
"transcript": [ { "speaker": "callee", "text": "We close at four.", "at": 1767... } ] }
Messages
POST /agent/messages
{ "numberId": "10", "to": "+14155551234", "body": "Running late, there at 3." }
Goes through the same carrier compliance checks as the app's own send, and lands in the same conversation thread you read in the app — not a separate agent-only log. Max 1600 characters.
Errors
| Status | Code | Means |
|---|---|---|
401 | invalid_api_key | Missing, wrong, or revoked key. |
400 | objective_required | A call with no stated purpose. |
400 | invalid_number | to isn't a dialable number. |
403 | a2p_registration_required | That line isn't cleared for texting yet. |
404 | — | The number or call isn't yours. |
429 | rate_limited | 30 calls or 100 texts an hour. Carries retryAfterSeconds. |
402 | ai_minutes_exhausted | You're out of AI minutes for the month. |
Quickstart
# Settings > Your Agent > Agent API key > Generate a key, then:
KEY=a2l_live_...
HOST=https://www.ai2ndline.com
# 1. Which line can this key use?
NUM=$(curl -s -H "X-API-Key: $KEY" $HOST/agent/numbers | jq -r '.numbers[0].numberId')
# 2. Make the call.
ROOM=$(curl -s -X POST $HOST/agent/calls/outbound -H "X-API-Key: $KEY" -H 'content-type: application/json' -d "{"numberId":"$NUM","to":"+14155551234",
"objective":"Ask their hours on Saturday."}" | jq -r .room)
# 3. Read what was said.
curl -s -H "X-API-Key: $KEY" $HOST/agent/calls/$ROOM | jq .transcript
MCP
Everything above is also reachable over MCP, with the same key, so an agent that speaks MCP needs no glue code at all.
claude mcp add --transport http ai2ndline https://www.ai2ndline.com/mcp --header "Authorization: Bearer $KEY"
Tools: list_numbers, place_call, check_call,
send_text. MCP is a transport, not a second implementation — both doors run the
same checks, so there is no way in through MCP that skips a rule the HTTP API enforces.
Get a key
Open the developer console — sign up, accept the terms, generate a key, and copy a ready-made MCP config. No iPhone required.
In the app it is Settings › Your Agent › Agent API key. Either way the key is shown once.
The console carries the whole product — subscribe, claim a number, generate a key, and set up the assistant itself: name, greeting, personality, voice, what it’s allowed to do, what it knows, your hours, who it transfers to, and your calendar. Nothing about getting started requires the app.