Your phone, as an API.
ApiSIM turns an Android phone you own into an endpoint. The base URL is https://api.apisim.app; every path below is relative to it. Everything is JSON, every write is idempotent, and every resource says whether it is real or simulated.
Quickstart
- Create a workspace, confirm your e-mail, and pair the phone with the Android gateway. Choose the business SIM on the phone.
- In the dashboard, create an API key. Keep it on your server; it never belongs in a browser or an app.
- List your lines, then send a message:
curl https://api.apisim.app/v1/lines \
-H "Authorization: Bearer $APISIM_KEY"
curl https://api.apisim.app/v1/messages \
-H "Authorization: Bearer $APISIM_KEY" \
-H "Idempotency-Key: welcome-1042" \
-H "Content-Type: application/json" \
-d '{"line_id":"LINE_ID","to":"+15555550101","body":"Hi Alex, can I help?"}'
# 202 Accepted → {"id":"…","state":"queued", …}
curl https://api.apisim.app/v1/operations/OPERATION_ID -H "Authorization: Bearer $APISIM_KEY"The phone picks the message up within a few seconds and reports sent when the carrier accepts it. Replies from that number arrive as messages.incoming events.
Authentication
Server-to-server calls use Authorization: Bearer <api key>. Keys are created and revoked in the dashboard (POST /v1/keys, DELETE /v1/keys/:id) and expire after 90 days unless rotated. The dashboard itself uses a session cookie with same-origin checks. Phones use separate device credentials that can only reach device endpoints for their own work; a device credential can never read your account or other phones.
Idempotency and outcomes
Every POST /v1/messages and POST /v1/calls requires an Idempotency-Key header (1 to 200 characters) that identifies one real-world action. Repeating the same key with the same content returns the original operation; the same key with different content is rejected with 409. After a timeout, retry with the same key.
States you will see on an operation, in order of certainty:
queued: accepted by the API, not yet on the phone.accepted: the phone has the command.sent/delivered: the carrier accepted the message / confirmed delivery. Acceptance is not delivery.ringing/active/completed: call lifecycle, from the phone’s own telephony receipts.failed: the phone or carrier refused; seeerror.uncertain: the phone went quiet mid-action. Reconcile from the dashboard or wait; never retry with a new key.
Messages
POST /v1/messages with line_id, to (E.164) and body (up to 1,600 characters; the phone splits long texts). Sending to a number opens a business scope for that line and number from that moment: replies and manual messages from the phone to that number are synchronised; older personal history is never imported. Read conversations with GET /v1/messages and outbound operations with GET /v1/operations.
Calls and live audio
POST /v1/calls starts an outgoing call from the line. The phone dials our telephone bridge, enters a one-time PIN, dials your recipient on the SIM and merges the two legs with the carrier’s conference feature, so the recipient sees your business number. Your software or browser joins the bridge with POST /v1/calls/:id/session, which returns a short-lived room-scoped WebRTC token: publish a microphone or an AI agent’s audio track and subscribe to the recipient’s audio. Incoming calls arrive as calls.incoming; answer with POST /v1/calls/:id/answer and hang up with POST /v1/calls/:id/end.
Calling is enabled per line after a conference-capability check. Live AI must use the streaming session; post-call transcripts are never a live response path. Bridge minutes beyond the plan allowance are charged from credits.
Recordings and transcripts
Set record: true and recording_notice_acknowledged: true when starting or answering a call, then POST /v1/calls/:id/recording once audio is connected. Recording happens at the bridge, not on the phone. GET /v1/recordings lists state (recording, ready, failed, uncertain) and GET /v1/recordings/:id/playback returns a two-minute private URL. Transcripts are produced asynchronously; transcript.ready fires when done. You are responsible for recording notices and consent; see the terms.
Events and webhooks
Everything that happens is an event with a per-workspace increasing seq. Poll GET /v1/events?after=SEQ and persist the last sequence you processed. Or register POST /v1/webhooks with a public HTTPS URL; we return a signing secret once.
X-Apisim-Event: evt_… X-Apisim-Signature: t=1730500000,v1=HEX # verify: HMAC-SHA256(secret, timestamp + "." + raw_body) == v1 # reject if |now - t| > 300 s · deduplicate on X-Apisim-Event · reply 2xx quickly
Delivery is at least once with exponential backoff for up to 12 attempts. Event types include sms.queued, sms.sent, sms.delivered, sms.failed, messages.incoming, calls.incoming, calls.active, calls.completed, recording.ready, transcript.ready and billing.*.
Phones and lines
A device is a paired phone; a line is one SIM on that device. GET /v1/devices shows online state (heartbeat within 15 seconds) and advertised capabilities (sms, calls, conference). GET /v1/lines lists lines with their numbers. Each connected line needs a seat; dual-SIM phones can host two lines.
Billing and credits
GET /v1/billing returns plan state (trial, subscribed, expired), seats, lines in use, credit balance and included bridge minutes. When the trial ends without a subscription, reads keep working but sends and calls return 402. Credits are debited only from completed recordings, transcripts and calls, never from failed or uncertain ones; GET /v1/billing/ledger shows every movement.
AI agents and MCP
Agents work best with the operation model above: send with a stable idempotency key derived from the agent’s own task ID, subscribe to events instead of polling in a loop, and treat uncertain as “ask a human”. For live voice, publish the agent’s audio into the call session and consume the recipient’s track in real time.
The ApiSIM MCP server exposes these endpoints as tools for Claude, Cursor and any Model Context Protocol client: list_lines, send_message, get_operation, list_conversation, start_call, answer_call, end_call, get_call_session, start_recording, list_recordings, get_recording_playback, list_events and billing_status. It lives in the mcp/ directory of the ApiSIM repository (npm publication is pending); build it with npm ci && npm run build and run it over stdio:
{
"mcpServers": {
"apisim": {
"command": "node",
"args": ["/path/to/apisim/mcp/dist/index.js"],
"env": { "APISIM_API_KEY": "sim_…" }
}
}
}Limits and fair use
- Volume is bounded by your phone, SIM and carrier. Expect tens to a few hundred conversations a day per line.
- One active call per device at a time. Recording requires credit; included bridge minutes reset monthly.
- Webhook targets must be public HTTPS on port 443; private and loopback addresses are rejected.
- Requests are rate-limited per workspace; a
429carries aRetry-Afterheader. - You must have consent to contact recipients and honour opt-outs. Spam and deception are grounds for suspension.