Skip to content
Markets open
Quickstart · Voice

Build an AI voice agent

Describe what the agent says and the rules it keeps. Rehearse it turn by turn with no call placed, then attach it to a dialer campaign that calls over the routes you choose.
Calls
POST /ai-agents · POST /ai-agents/{id}/simulate · PUT /dialer/campaigns/{id}
Scope
dialer:write
Cost
Creating and simulating an agent is free. On live calls, one flat rate per AI-connected minute ($0.25 by default, billed per second) is added to the route cost.

Get an API key

Open an account, then create a key under Dashboard, API keys. Live keys (wmmn_live_sk_) send real traffic and charge your balance. Test keys (wmmn_test_sk_) run the same request through routing and pricing without delivering it, and draw on test credit, which comes with an invite code.

A key restricted to scopes needs dialer:write. GET /ai-agents/voices lists the voices the agent can speak with.

Every sample reads the key from the environment. Keep it out of source control.

export PACKETEXCHANGE_API_KEY=wmmn_live_sk_...

Fund the balance

PacketExchange is prepaid. Top up from $5 by card or crypto, or from $100 by wire, under Dashboard, Billing. An agent can top itself up with x402. There is no contract and no monthly fee.

Nothing is billed until the agent is on a live campaign call. Then the route is billed as a normal call, plus the AI-connected minutes.

CostCreating and simulating an agent is free. On live calls, one flat rate per AI-connected minute ($0.25 by default, billed per second) is added to the route cost.

Make the request

Create the agent with a name, the first thing it says and its instructions (systemPrompt), then send one line as the person on the call and read the agent's reply.

# 1. Create the agent: what it says first, and the instructions it follows.
agent_id=$(curl -s https://packetexchange.io/api/v1/ai-agents \
  -H "Authorization: Bearer $PACKETEXCHANGE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Appointment confirmation",
    "language": "en",
    "firstMessage": "Hello, this is Riverside Clinic calling to confirm your appointment tomorrow at 10:30.",
    "systemPrompt": "You confirm appointments for Riverside Clinic. Ask whether the person can attend tomorrow at 10:30. Keep every reply short and polite.",
    "maxCallSeconds": 180
  }' | jq -r .data.id)

# 2. Rehearse one turn. No call is placed and nothing is billed.
curl -s "https://packetexchange.io/api/v1/ai-agents/$agent_id/simulate" \
  -H "Authorization: Bearer $PACKETEXCHANGE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"message": "Yes, I can still make it."}' | jq .data
Save as request.sh, run bash request.shNeeds curl, plus jq for the multi-step samples

Handle the response

Creating returns the agent. Each simulated turn returns what the agent would say and what it would do next.

201 CreatedPOST /ai-agents
{
  "success": true,
  "data": {
    "id": "<uuid>",
    "name": "Appointment confirmation",
    "language": "en",
    "firstMessage": "Hello, this is Riverside Clinic calling to confirm...",
    "systemPrompt": "You confirm appointments for Riverside Clinic...",
    "maxCallSeconds": 180,
    "enabled": <boolean>,
    "createdAt": "<ISO-8601 UTC>"
  }
}
200 OKPOST /ai-agents/{id}/simulate
{
  "success": true,
  "data": {
    "reply": "<what the agent would say>",
    "action": "continue",
    "captured": {}
  }
}

Values in angle brackets are placeholders for your own ids, times and amounts.

action
continue, end or transfer: what the agent would do after this turn.
captured
Structured details picked up this turn, such as a name or an intent.
history (request)
Pass earlier turns to simulate a whole conversation, up to 60 of them.
guardrails (request)
Hard rules the agent must never break, kept separate from the script.

When something goes wrong

  • Simulation is rate limited to 20 requests per minute.
  • To go live, PUT /dialer/campaigns/{id} with { "aiAgentId": "<agent id>" } on a campaign that is a draft, ready or paused.
  • Errors use the standard envelope with a VALIDATION_ERROR per bad field.
400The error envelope
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid input",
    "details": [
      { "path": "to", "message": "<why the field was refused>" }
    ]
  }
}

Go further

Start building

Run it with your own key

Open an account, add credit from $5 and create a key. You pay the carrier's rate plus a fee capped at $0.001.