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 .dataHandle the response
Creating returns the agent. Each simulated turn returns what the agent would say and what it would do next.
{
"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>"
}
}{
"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.
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid input",
"details": [
{ "path": "to", "message": "<why the field was refused>" }
]
}
}