Onboard yourself — no key from us
There is no waiting list and no sales call. The agent walks the whole path on its own: it proves who it is, its operator passes KYC once, and from then on it pays for its own calls in USDC. We never hand out credentials by hand.
| Step | Call | What happens |
|---|---|---|
| 1. Identity | AgentKey.generate() | The agent makes an Ed25519 keypair. The public half becomes its did:key — its permanent name. Keep the seed; lose it and the agent loses its identity. |
| 2. Register | POST /v1/agents/register | Binds that public key to a fresh agent_id. Public endpoint — no authentication needed. |
| 3. KYC | POST /v1/operators/{id}/onboard | The operator behind the agent verifies itself once through Stripe. Until this passes, no credential is issued. |
| 4. Credential | POST /v1/agents/{id}/credential | Returns a W3C Verifiable Credential — but only to whoever can sign with the agent's private key. Proof of possession, not a password. |
| 5. Log in | /v1/auth/challenge → /v1/auth/verify | Challenge–response over Ed25519. Returns a session token tied to that agent. Banned or suspended agents are refused here, even holding a valid credential. |
| 6. Fund | POST /v1/billing/{id}/wallet | Link a Base address, then send USDC to the receiving address. Credited automatically once confirmed on chain. |
| 7. Call | POST /v1/agent-call | With the session token in Authorization: Bearer. The agent pays from its own balance; agent_id in the body is neither needed nor accepted from someone else's session. |
The whole path in code
from voicegate import VoiceGate, AgentKey
vg = VoiceGate() # no api_key anywhere
key = AgentKey.generate() # persist key.seed_b64() somewhere safe
reg = vg.kya_register(key, operator_email="ops@example.com", domain="example.com")
# → the operator passes KYC once (Stripe), then:
vc = vg.kya_get_credential(key, reg["agent_id"])
vg.kya_login(key, vc["credential"]) # session token, held by the client
vg.link_wallet(reg["agent_id"], "0x…") # then send USDC on Base
vg.call(
to="+14155550123",
greeting="Hi, this is Ada calling on behalf of Nordwind Studio.",
prompt="Confirm tomorrow's 3pm appointment.",
)
x-api-key instead: one key, many agents, each still billed to its own balance. That path does involve talking to us — the self-service path above does not.
Quickstart
Install the SDK and place a call. The SDK is open source under MIT and wraps the same REST API documented below.
pip install voicegate # Python
npm i voicegate # Node
Python
from voicegate import VoiceGate
vg = VoiceGate(
api_key="vg_live_...",
base_url="https://aivoicegate.com",
)
call = vg.call(
to="+14155550123",
greeting="Hi, this is Ada calling on behalf of Nordwind Studio.",
prompt=(
"You are booking a table for four at 7pm on Friday. "
"If 7pm is unavailable, accept anything between 6:30pm and 8pm. "
"Confirm the name Nordwind and thank them."
),
language="en",
voice="female",
)
print(call["call_uuid"], call["rate_micros_per_min"])
Node
import { VoiceGate } from "voicegate";
const vg = new VoiceGate({
apiKey: process.env.VOICEGATE_API_KEY,
baseUrl: "https://aivoicegate.com",
});
const call = await vg.call({
to: "+14155550123",
greeting: "Hi, this is Ada calling on behalf of Nordwind Studio.",
prompt: "You are booking a table for four at 7pm on Friday.",
language: "en",
voice: "female",
});
console.log(call.call_uuid, call.rate_micros_per_min);
Plain HTTP
curl -X POST https://aivoicegate.com/v1/agent-call \
-H "x-api-key: vg_live_..." \
-H "content-type: application/json" \
-d '{
"to": "+14155550123",
"greeting": "Hi, this is Ada calling on behalf of Nordwind Studio.",
"prompt": "You are booking a table for four at 7pm on Friday.",
"language": "en",
"voice": "female"
}'
Authentication
Every endpoint except discovery, registration and public identity lookups requires an API key in the x-api-key header. Base URL:
https://aivoicegate.com/v1
/v1/auth/challenge then /v1/auth/verify) and receive a session token — no shared secret to leak. See the KYA layer.
Place a call
Places a real outbound call over our own telephony. The greeting is spoken as soon as the line connects; from then on VoiceGate transcribes the other side, feeds it to the agent brain, and speaks the reply — turn by turn, until either side hangs up.
Body
| Field | Type | Description |
|---|---|---|
to required | string | Destination number in E.164, e.g. +14155550123. 6–15 digits, leading + optional. |
greeting required | string, ≤500 | The first sentence spoken when the call connects. Say who is calling and on whose behalf — in most jurisdictions that is not optional. |
prompt required | string, ≤4000 | Instructions that drive the agent's side of the conversation: the goal, the acceptable outcomes, and when to end the call. |
language optional | string | Spoken language, e.g. en, es, fr, de, zh. Defaults to en. The agent stays in this language for the whole call. |
voice optional | male | female | Voice used for speech. Defaults to female. |
agent_id optional | uuid | Your KYA agent id. When present, the call is charged to that agent's balance and refused if the agent is banned. |
caller_id optional | string, ≤64 | Number to present to the callee, if your account has a verified one. |
Response 200
{
"status": "calling",
"call_uuid": "8f3c1e6a-...",
"channel": "single",
"billed": "per-minute",
"hold_micros": 600000,
"rate_micros_per_min": 60000,
"destination_region": "US/Canada (NANP)"
}
hold_micros is what was reserved for the maximum call length, not what you will pay. rate_micros_per_min is in millionths of a USDC: 60000 means $0.06 per minute. The final charge lands on the balance when the call ends.
Discovery and x402
A GET on the paid resource returns 402 Payment Required with x402 v2 payment requirements — the price, the asset, the chain and the address to pay. An agent that has never seen VoiceGate can discover the cost and settle without a human, and x402 indexes can list the resource without holding a key.
{
"x402Version": 1,
"error": "payment required",
"accepts": [{
"scheme": "exact",
"network": "eip155:8453",
"amount": "60000",
"asset": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
"payTo": "0x...",
"resource": "https://aivoicegate.com/v1/agent-call",
"description": "VoiceGate outbound call, prepaid per minute",
"maxTimeoutSeconds": 120,
"extra": { "name": "USD Coin", "version": "2" }
}],
"resource": { "url": "...", "description": "...", "mimeType": "application/json" }
}
The asset is native USDC on Base (6 decimals) and amount is in atomic units, so 60000 is $0.06. A 402 from the POST endpoint carries the same structure with the amount needed for that specific call.
Balance, wallet and history
Current balance, what is on hold, and the terms the account is charged under.
{
"agent_id": "…",
"balance_micros": 970000,
"held_micros": 0,
"available_micros": 970000,
"balance_usdc": 0.97,
"daily_cap_micros": 5000000,
"daily_spent_micros": 30000,
"rate_micros_per_min": 60000,
"max_call_seconds": 600,
"pay_to": "0x…"
}
available_micros is balance minus holds — that is what a new call is checked against. pay_to is where to send USDC to top up.
Links a Base address to the agent. Incoming USDC from that address credits this agent's balance automatically. Body: {"wallet_address": "0x…"} — a 40-hex-digit address. Without this link a transfer still arrives at our address but cannot be attributed to you, so link the wallet before sending anything.
Append-only ledger for the account: topup, hold, release, charge, refund, adjust. Every entry carries an amount in micro-USDC, a timestamp and a reference — for a top-up, the on-chain transaction hash.
Agent identity (KYA)
Registers an agent and its operator. Returns an agent_id with kyc_status: "pending". All fields are optional, but an agent with a public key and a registration URI can later prove who it is instead of just holding a secret.
curl -X POST https://aivoicegate.com/v1/agents/register \
-H "content-type: application/json" \
-d '{
"operator_email": "ops@example.com",
"operator_kind": "business",
"domain": "example.com",
"public_key": "z6Mk…",
"registration_uri": "https://example.com/.well-known/agent.json",
"erc8004_agent_id": "…"
}'
Public trust lookup: KYC status, reputation and whether the agent is banned. Anyone can call it — that is the point. The party on the other end of a call should be able to check who is calling.
How billing actually works
Money is held in micro-USDC — millionths of a dollar — because per-second billing at $0.06 a minute produces fractions of a cent, and a two-decimal column loses them.
The lifecycle of one call
| Step | What happens |
|---|---|
| 1. Top up | You send USDC on Base to the address from pay_to. Our scanner sees the confirmed transfer and credits the agent whose wallet matches the sender. Crediting is idempotent — the same transaction can never be counted twice. |
| 2. Hold | Before dialling, VoiceGate reserves the cost of the maximum call length. Two calls started at once cannot both spend the same balance: the reservation is taken under a row lock. |
| 3. Talk | The call happens. Ringing time is not billable. |
| 4. Settle | When the call ends, the switch reports the billable seconds. You are charged for those, rounded up to the second, and the rest of the hold is released immediately. |
| 5. Never dialled | If the call fails to originate, the hold is released at once. A hold that somehow outlives its call is reaped automatically — no balance stays frozen. |
A daily spending cap applies per agent (default $5/day during beta) as a blast radius limit: a looping agent burns its cap, not your whole balance.
Coverage and rates
VoiceGate is deny-by-default. A destination that is not explicitly allowed is refused with 403 before any money moves or any line opens.
| Destination | Rate | Status |
|---|---|---|
| United States, Canada (+1) | $0.06/min | Available |
| Ukraine (+380) | $0.09/min | Pending carrier verification |
| Premium-rate (+1900, +1976) | — | Blocked permanently |
| Satellite (+870, +881–883) | — | Blocked permanently |
| Caribbean inside +1 (Jamaica, Dominican Republic, and others) | — | Blocked permanently |
+1876 looks like a US number and is Jamaica; +1809 is the Dominican Republic. Termination there costs dollars a minute, not cents. A flat rate across everything under +1 is the classic way a voice API gets drained overnight — so the expensive ranges are matched by longest prefix and refused.
More countries open as carrier verification completes. If you need a specific destination, ask — the list is a policy table, not a technical limit.
Errors
Errors are JSON with a stable error code. Read the code, not the prose.
| Status | Code | Meaning |
|---|---|---|
| 400 | bad_request | Body failed validation. detail lists the offending fields. |
| 400 | bad_number | to is not a plausible E.164 number. |
| 401 | unauthorized | Missing or unknown API key. |
| 402 | payment_required | Not enough available balance. The body carries x402 payment requirements for the amount needed. |
| 403 | destination_not_allowed | That destination is not on the allowed list. |
| 403 | agent_banned | The agent is banned from the network. |
| 403 | daily_cap_reached | The agent spent its daily cap. Resets at midnight UTC. |
| 404 | agent_not_found | No agent with that id. |
| 409 | wallet_already_linked | That address is already bound to another agent. |
| 429 | rate_limited | Too many requests. retry_after_sec and the Retry-After header tell you when to come back. |
| 502 | telephony_error | The call could not be placed. Any hold is released automatically — you are not charged. |
Rate limits
| Scope | Limit | Window |
|---|---|---|
| Whole API, per IP | 120 requests | 1 minute |
| Call endpoint, per API key | 6 calls | 1 minute |
| Agent registration, per IP | 10 registrations | 1 hour |
Every 429 carries a Retry-After header. Respect it; retrying into a limit only extends it.
Acceptable use
VoiceGate is a narrow, clean service and stays that way on purpose.
Allowed: consented task calls, bookings and scheduling, status and availability checks, expected callbacks.
Not allowed: cold mass-calling without consent, deceptive impersonation of a person or company, fraud or pressure scenarios, calls to purchased lists. Agents doing this lose the line and carry the record on their portable reputation.
Need a destination that is not open yet, or an integrator key for many agents? Tell us what you need →. For anything else: support@aivoicegate.com.