wixzel_phone
The official Dart SDK for the Wixzel Phone API: AI voice agents that place and answer real phone calls over your own SIP trunk. One API key, one prepaid balance, every voice engine.
Works in Dart and in Flutter, including on the web.
- Every
/v1endpoint as a typed method, checked against the API's OpenAPI document by a test. - Cursor pagination as a
Stream. - Automatic idempotency keys on the two paths that spend money, so a retried timeout never dials twice.
- Retries on rate limits (honouring
Retry-After) and on transient failures, only where a retry is safe. - Errors that carry the API's stable
code, therequestIdto quote, and thedocUrlto read. - One dependency:
package:http.
dart pub add wixzel_phone
Quickstart
import 'package:wixzel_phone/wixzel_phone.dart';
final client = WixzelPhone(apiKey: 'wv_live_…');
// What can the platform serve right now, and at what price?
final engines = await client.engines.list();
final agent = await client.agents.create(CreateAgent(
name: 'Support',
systemPrompt: 'You are a concise support agent.',
openingMessage: 'Hi, how can I help?',
voice: VoiceConfig.composed(
stt: const SttConfig(model: 'deepgram/nova-3'),
llm: const LlmConfig(model: 'openrouter/gpt-4o-mini'),
tts: const TtsConfig(model: 'elevenlabs/eleven_turbo_v2_5'),
),
));
// Spends credit and rings a real phone. An Idempotency-Key is generated for you.
final call = await client.calls.create(
CreateCall(to: '+14155551234', agentId: agent.id),
);
final detail = await client.calls.retrieve(call.id);
print('${detail.status.value} ${detail.costMicros} ${detail.transcript.length} turns');
client.close();
Or hand the whole turn to one model:
voice: VoiceConfig.realtimeModel(
const RealtimeConfig(model: 'google/gemini-live-2.5-flash', voice: 'Charon'),
),
Pagination
Every list returns a Page. Stream it to walk every page; the cursor and your query are carried along.
final calls = await client.calls.list(status: CallStatus.completed, limit: 100);
await for (final call in calls.autoPaging()) {
print('${call.id} ${call.durationSeconds}s');
}
// Or page by page
var page = await client.leads.list(tag: 'clinic');
while (page != null) {
print('${page.data.length} leads, more: ${page.hasMore}');
page = await page.nextPage();
}
Errors
Match on code; the message may be reworded.
try {
await client.calls.create(CreateCall(to: to, agentId: agentId));
} on WixzelException catch (e) {
if (e.code == 'insufficient_credits') print('balance: ${e.balance}');
print('${e.statusCode} ${e.code} ${e.requestId} ${e.docUrl}');
} on WixzelConnectionException catch (e) {
// Never got an answer, even after retries. Retrying a keyed request is safe.
print(e.message);
}
Response enums carry an unknown fallback, so a value the API adds tomorrow will not throw in an app shipped today.
Idempotency
calls.create and billing.createTopup require an Idempotency-Key; the SDK generates one per call and reuses it across its own retries. Pass your own to make a retry from your side safe too, and use the …WithResponse variants to see whether the server replayed an earlier request:
final result = await client.calls.createWithResponse(
CreateCall(to: to, agentId: agentId),
idempotencyKey: 'order-$orderId',
);
if (result.idempotentReplay) print('the server had already placed this call');
print(result.data.id);
Retries
429: retried afterRetry-After(capped at 10 seconds), up tomaxRetries(default 2). Rate-limited requests are never charged.- Network failures, timeouts and
502–504: retried only forGETand for requests carrying an idempotency key.DELETE,PATCHand unkeyedPOST(hang up, start, pause, test, rotate) are never repeated on an ambiguous failure. 500and other4xx: never retried.
Pinning a version
final client = WixzelPhone(apiKey: apiKey, apiVersion: '2026-09-01');
Sends Wixzel-Version so an upgrade is something you do rather than something that happens to you.
Options
| Option | Default | |
|---|---|---|
apiKey |
required | wv_live_… or wv_test_… |
baseUrl |
https://api.phone.wixzel.com |
For a self-hosted API |
apiVersion |
none | Wixzel-Version date pin |
timeout |
30 s | Per attempt |
maxRetries |
2 | Retries after the first attempt |
httpClient |
one of its own | Pass your own; then closing it is your job |
defaultHeaders |
none | Sent on every request |
client.request(method, path, …) reaches any endpoint the SDK does not model yet, with the same auth, retry and error handling. client.close() releases the HTTP client when the SDK created it.
Flutter
The package is pure Dart with no dart:io in its public path, so it runs on every Flutter target including the web. Keep live keys out of a shipped app: on mobile and web, call your own backend, and let it hold the key.
Related
- Documentation · API reference
- Source, where issues and pull requests are read. The SDK is developed in the private monorepo that also holds the API, so a change to an endpoint and the change to its client land together, and is mirrored here on release.
wixzel-phone, the same SDK for TypeScript and JavaScriptwixzel-phone-mcp, the MCP server for Claude Code and claude.ai
MIT.
Libraries
- wixzel_phone
- The official Dart SDK for the Wixzel Phone API: AI voice agents that place and answer real phone calls over your own SIP trunk.