The official PHP client for the VoiceTel REST API β provision numbers, place orders, validate e911, send messages, and manage your account, with modern PHP 8.1+ ergonomics and battle-tested Guzzle transport.
- Features
- Installation
- Quickstart
- Authentication
- Resource Reference
- Error Handling
- Rate Limits
- Development
- API Documentation
- Contributors
- Sponsors
- License
- PHP 8.1+ end-to-end β readonly properties, native enums, named arguments, typed everywhere.
ErrorKindenum for HTTP error classification:BadRequest,Authentication,PermissionDenied,NotFound,Conflict,RateLimit,Server,Unknown.- PSR-4 autoloading under
VoiceTel\Sdk\, plays nicely with Symfony, Laravel, and every other Composer-aware framework. - PHPStan level 8 clean across the entire
src/tree.
- Built on Guzzle 7 β the de-facto HTTP client in the PHP ecosystem.
- Automatic retry with exponential backoff on 429 / 5xx β honors
Retry-Afterheaders, capped at 8s. - Configurable timeout per client (defaults to 30s).
- Bearer auth managed for you; the password β key exchange is one method call (
$client->login(...)). - Structured
ApiErrorwith typedkindso you canmatch ($e->kind) { ErrorKind::RateLimit => ... }without parsing HTTP status codes. - Envelope-aware β strips the
{"status":"success","data": ...}wrapper before returning the inner payload.
- 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 checks.
- 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.
- 101 unit tests at 94%+ line coverage with PHPUnit 10.
- Mocked Guzzle via
GuzzleHttp\Handler\MockHandlerβ every method and every error path exercised, no network in CI. - Read-only integration scaffolding gated by
VOICETEL_USERNAME/VOICETEL_PASSWORDenv vars.
- Zero codegen footprint β every byte hand-written.
- Single Composer package, single namespace, no surprise transitive deps beyond Guzzle.
composer require voicetel/sdkRequires PHP 8.1 or later and the json extension (bundled with PHP).
<?php
require __DIR__ . '/vendor/autoload.php';
use VoiceTel\Sdk\Client;
$client = new Client();
// Exchange username + password for an API key (one-time per session)
$client->login(1000000001, 'hunter2');
// Typed responses β arrays with documented shapes.
$me = $client->account->get();
printf("Balance: \$%.2f | Caller ID: %s\n", $me['cash'], $me['callerId']);
// List your numbers
$list = $client->numbers->list();
foreach ($list['numbers'] as $n) {
printf(
"%s route=%d cnam=%s sms=%s\n",
$n['number'],
$n['route'],
$n['cnam'] ? 'yes' : 'no',
$n['smsEnabled'] ? 'yes' : 'no',
);
}Or, if you already have an API key, skip login() and pass it straight to the constructor:
$client = new Client(apiKey: getenv('VOICETEL_API_KEY'));
$coverage = $client->iNumbering->coverage(['state' => 'NJ']);
foreach ($coverage['coverage'] as $bucket) {
printf("%s-%s: %d TNs available\n", $bucket['npa'], $bucket['nxx'], $bucket['count']);
}Every endpoint requires Authorization: Bearer <apikey> except POST /v2.2/account/api-key, which exchanges username + password for a fresh key. $client->login() 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.
$client = new Client();
$key = $client->login(1000000001, 'hunter2');
// $key is the new 32-hex bearer; the client already has it installed.| Resource | Property on Client | Example |
|---|---|---|
| Account | $client->account |
$client->account->cdr(1700000000, 1700100000) |
| ACL | $client->acl |
$client->acl->add(['acl' => [['cidr' => '1.2.3.0/24']]]) |
| Authentication | $client->authentication |
$client->authentication->update(['authType' => 1]) |
| e911 | $client->e911 |
$client->e911->validate(['address1' => '1 Way', ...]) |
| Gateways | $client->gateways |
$client->gateways->list() |
| iNumbering | $client->iNumbering |
$client->iNumbering->searchInventory(['npa' => 201]) |
| Lookups | $client->lookups |
$client->lookups->lrn('2015551234', '2125550000') |
| Messaging | $client->messaging |
$client->messaging->send(['fromNumber' => ..., 'toNumber' => ..., 'text' => ...]) |
| Numbers | $client->numbers |
$client->numbers->assignCampaign('2015551234', ['campaignId' => 'CXXXXXX']) |
| Support | $client->support |
$client->support->create(['subject' => '...', 'message' => '...']) |
messaging->send()uses the wire field namesfromNumberandtoNumberβ pass them exactly as shown.support->create()/support->get()return a conversation whosenumberfield is a ticket sequence integer (e.g. 1015), not a phone number β keep that distinction in mind everywhere else in this API wherenumberis a 10-digit TN.- LIDB endpoints (
numbers->setLidb()) use the spellingLidbconsistently β the legacyLibdtypo from earlier spec drafts has been corrected. iNumbering->portAvailability()includes the v2.2.10 fieldslocalRoutingNumberandrateCenterTieralongside the originalnumber,portable,losingCarrier, andreason.DELETEendpoints generally return204 No Contentβ the corresponding methods returnvoid. Three endpoints intentionally return a body and anarray:acl->remove(),numbers->unassignCampaign(), andnumbers->bulkUnassignCampaign().
All HTTP errors throw VoiceTel\Sdk\ApiError. Inspect kind, statusCode, code(), or body:
| 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 |
use VoiceTel\Sdk\ApiError;
use VoiceTel\Sdk\ErrorKind;
try {
$n = $client->numbers->get('9999999999');
} catch (ApiError $e) {
match ($e->kind) {
ErrorKind::NotFound => print "That number isn't on your account.\n",
ErrorKind::RateLimit => print "Slow down β backoff and retry.\n",
default => throw $e,
};
}Or use the helper predicates:
catch (ApiError $e) {
if ($e->isNotFound()) { /* ... */ }
if ($e->isRateLimit()) { /* ... */ }
if ($e->isConflict()) {
// $e->body is the structured AclConflictData / AuthPutConflictData payload.
}
}These endpoints are limited to 6 requests per hour per IP:
account/info($client->account->get())account/cdraccount/recurring-chargesaccount/paymentsaccount/registrationaccount/api-key($client->login())
The SDK automatically retries 429 responses with Retry-After honored, up to maxRetries (default 2). To bump it:
$client = new Client(
apiKey: getenv('VOICETEL_API_KEY'),
maxRetries: 4,
timeout: 60.0,
);git clone https://github.com/voicetel/php-sdk
cd php-sdk
# Install dependencies
composer install
# Run unit tests
vendor/bin/phpunit --testsuite unit
# With coverage (needs Xdebug or PCOV)
XDEBUG_MODE=coverage vendor/bin/phpunit --testsuite unit --coverage-text
# Static analysis
vendor/bin/phpstan analyse
# Run read-only integration tests against a real account
cp .env.example .env # populate VOICETEL_USERNAME / VOICETEL_PASSWORD
set -a; source .env; set +a
vendor/bin/phpunit --testsuite 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.