The official C# client for the VoiceTel REST API — provision numbers, place orders, validate e911, send messages, and manage your account, all with strongly-typed, async/await-friendly C#.
- Features
- Installation
- Quickstart
- Authentication
- Resource Reference
- Error Handling
- Cancellation and Timeouts
- Rate Limits
- Development
- API Documentation
- Contributors
- Sponsors
- License
- Native C# classes for every one of the 73 API operations — serialized with
System.Text.Json, no reflection magic. - Nullable reference types throughout — distinguish "not set" from "empty" cleanly when PATCH-ing.
- Nullable response fields —
ForwardToisstring?so you can tell apart "no forward configured" from an empty destination. CancellationTokeneverywhere. Every method takes aCancellationTokenas the last argument; cancellation and timeouts propagate cleanly down to the HTTP layer.
- Built on
System.Net.Http.HttpClient— no third-party dependencies onnet8.0. - Automatic retry with exponential backoff on 429 / 5xx — honors
Retry-Afterheaders, capped at 8s. - Configurable retries and
HttpClient— bring your own for connection pooling, instrumentation, or HTTP/2. - Bearer auth managed for you; the password→key exchange is one method call (
client.LoginAsync). - Structured
ApiErrorexception with typedKindenum so you canswitch (ex.Kind) { case ErrorKind.RateLimit: ... }without parsing HTTP status codes.
- Numbers — list, get, add, remove, route, translate, CNAM, LIDB, fax, forward, SMS, messaging campaigns, port-out PIN, account moves.
- Account — profile, sub-accounts, CDRs, credits, payments, MRC, registration, password recovery.
- e911 — record provisioning, address validation, lookup, removal.
- Gateways — list, create, update, delete, view bound numbers.
- Messaging — SMS & MMS sending, message history, 10DLC brand and campaign registration, per-number messaging state.
- Lookups — CNAM and LRN dips.
- iNumbering — inventory search, coverage queries, number orders, port-in submissions, port-out availability (with v2.2.10
localRoutingNumberandrateCenterTierfields). - Support — ticket create / read / update / delete, threaded messages, replies.
- ACL — IP allowlist management with structured 409 conflict bodies.
- Authentication — switch between Digest, IP-only, or hybrid modes; rotate passwords.
- xUnit unit tests that exercise every public method and every error path.
- HttpMessageHandler mocks — no network calls in unit tests.
dotnet buildclean withTreatWarningsAsErrorson bothnet8.0andnetstandard2.1.
- Zero codegen footprint — every byte hand-written.
- Single NuGet package:
VoiceTel.Sdk. netstandard2.1for broad reach (Unity, Xamarin, .NET Framework 4.8 via shim) andnet8.0for first-class modern .NET.
dotnet add package VoiceTel.SdkOr <PackageReference> it directly:
<PackageReference Include="VoiceTel.Sdk" Version="2.2.10" />Targets .NET 8.0 (preferred) and .NET Standard 2.1.
using VoiceTel.Sdk;
using var client = new VoiceTelClient();
// Exchange username + password for an API key (one-time per session)
await client.LoginAsync(username: 1000000001, password: "hunter2");
// Typed responses — your IDE knows what `me` is.
var me = await client.Account.GetAsync();
Console.WriteLine($"Balance: ${me.Cash:F2} | Caller ID: {me.CallerId}");
// List your numbers
var numbers = await client.Numbers.ListAsync();
foreach (var n in numbers.Numbers)
{
Console.WriteLine($"{n.Number} route={n.Route} cnam={n.Cnam} sms={n.SmsEnabled}");
}Or, if you already have an API key:
using var client = new VoiceTelClient(apiKey: "32hex...");
var coverage = await client.INumbering.CoverageAsync(new() { State = "NJ" });
foreach (var bucket in coverage.Coverage)
{
Console.WriteLine($"{bucket.Npa}-{bucket.Nxx}: {bucket.Count} TNs available");
}Every endpoint requires Authorization: Bearer <apikey> except POST /v2.2/account/api-key, which exchanges username + password for a fresh key. LoginAsync() handles the exchange and installs the returned key on the client.
Re-fetch the API key after any password change — the old one is invalidated.
Don't have credentials yet? Get them at voicetel.com/docs/api/v2.2/credentials.
using var client = new VoiceTelClient();
var key = await client.LoginAsync(1000000001, "hunter2");
// `key` is the new 32-hex bearer; the client already has it installed.| Resource | Property on Client | Example |
|---|---|---|
| Account | client.Account |
await client.Account.CdrAsync(t1, t2) |
| ACL | client.Acl |
await client.Acl.AddAsync(new AclModifyRequest { ... }) |
| Authentication | client.Authentication |
await client.Authentication.UpdateAsync(new AuthPutRequest { AuthType = AuthTypes.IpAuth }) |
| e911 | client.E911 |
await client.E911.ValidateAsync(new E911AddressRequest { ... }) |
| Gateways | client.Gateways |
await client.Gateways.ListAsync() |
| iNumbering | client.INumbering |
await client.INumbering.SearchInventoryAsync(new() { Npa = 201 }) |
| Lookups | client.Lookups |
await client.Lookups.LrnAsync("2015551234", "2012548000") |
| Messaging | client.Messaging |
await client.Messaging.SendAsync(new MessageSendRequest { ... }) |
| Numbers | client.Numbers |
await client.Numbers.AssignCampaignAsync("2015551234", ...) |
| Support | client.Support |
await client.Support.CreateAsync(new TicketCreateRequest { ... }) |
Optional request fields use nullable types. Initialize only what you want to change:
await client.Account.UpdateAsync(new AccountPutRequest
{
Timezone = "America/Chicago",
NotifyThreshold = 5,
Notify = true,
});All HTTP errors throw ApiError. Switch on Kind:
| Kind | HTTP status |
|---|---|
ErrorKind.BadRequest |
400 |
ErrorKind.Authentication |
401 |
ErrorKind.PermissionDenied |
403 |
ErrorKind.NotFound |
404 |
ErrorKind.Conflict |
409 |
ErrorKind.RateLimit |
429 |
ErrorKind.Server |
5xx |
ErrorKind.Unknown |
other / transport |
try
{
var n = await client.Numbers.GetAsync("9999999999");
Console.WriteLine($"Got: {n.Number}");
}
catch (ApiError ex) when (ex.IsNotFound)
{
Console.WriteLine("That number isn't on your account.");
}
catch (ApiError ex) when (ex.IsRateLimit)
{
Console.WriteLine("Slow down — backoff and retry.");
}
catch (ApiError ex)
{
Console.WriteLine($"status={ex.StatusCode} code={ex.Code} body={ex.Body}");
}ex.Body preserves the parsed response body — useful for 409 conflicts where the server returns structured detail (see AclConflictData, AuthPutConflictData).
Every method takes a CancellationToken. Use it for cancellation and per-call deadlines:
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
var me = await client.Account.GetAsync(cts.Token);For a global per-request timeout, configure the HttpClient:
var http = new HttpClient { Timeout = TimeSpan.FromSeconds(45) };
using var client = new VoiceTelClient(apiKey: "...", httpClient: http);These endpoints are limited to 6 requests per hour per IP:
account/info(client.Account.GetAsync)account/cdr(client.Account.CdrAsync)account/recurring-charges(client.Account.RecurringChargesAsync)account/payments(client.Account.PaymentsAsync)account/registration(client.Account.RegistrationAsync)account/api-key(client.LoginAsync)
The SDK automatically retries 429 responses with Retry-After honored, up to maxRetries (default 2). To bump it:
using var client = new VoiceTelClient(
apiKey: key,
maxRetries: 4);git clone https://github.com/voicetel/csharp-sdk
cd csharp-sdk
# Restore and build
dotnet build VoiceTel.Sdk.sln
# Unit tests (no network)
dotnet test --filter "Category!=Integration"
# With coverage
dotnet test --collect:"XPlat Code Coverage"
# Integration tests (read-only) — requires real credentials
export VOICETEL_USERNAME=1000000001
export VOICETEL_PASSWORD=hunter2
dotnet test --filter "Category=Integration"- Reference docs: voicetel.com/docs/api/v2.2/
- Interactive playground: voicetel.com/docs/api/v2.2/playground/ — try the API in your browser without writing any code
- API credentials: voicetel.com/docs/api/v2.2/credentials/
- Michael Mavroudis — Lead Developer
Contributions welcome. Open an issue describing the change, or send a pull request against main.
| Sponsor | Contribution |
|---|---|
| VoiceTel Communications | Primary development and production hosting |
This project is licensed under the MIT License — see the LICENSE file for details.