Skip to content

PHP SDK

The official PHP SDK wraps the public API with built-in retries and a typed exception hierarchy. PHP 8.1+.

Terminal window
composer require zulaiy/zutax

Pulls in guzzlehttp/guzzle.

  1. Sign in to your ZUTAX workspace.
  2. Settings → API keysCreate key.
  3. Copy the key — you’ll only see it once.

Use a staging key against https://staging-api.getzutax.com until you’re ready to go live.

use Zutax\ZutaxClient;
$client = new ZutaxClient(
apiKey: 'zk_test_...',
baseUrl: 'https://staging-api.getzutax.com',
);
$result = $client->invoices->transmit(
payload: [
'external_id' => 'INV-2026-0042',
'issue_date' => '2026-05-03',
'due_date' => '2026-06-03',
'customer' => [
'external_id' => 'C-100',
'legal_name' => 'Acme Industries Limited',
'tin' => '12345678-0001',
'address' => [
'city_name' => 'Lagos',
'country_code' => 'NG',
],
],
'lines' => [
[
'name' => 'Consulting services — May 2026',
'quantity' => '1',
'unit_price_naira' => '250000.00',
'vat_rate' => '7.5',
],
],
],
idempotencyKey: 'INV-2026-0042',
);
echo $result['invoice_id'] . PHP_EOL;

Every HTTP error surfaces as a typed exception:

use Zutax\Exception\AuthException;
use Zutax\Exception\ConflictException;
use Zutax\Exception\NotFoundException;
use Zutax\Exception\RateLimitException;
use Zutax\Exception\ServerException;
use Zutax\Exception\ValidationException;
use Zutax\Exception\ZutaxException;
try {
$client->invoices->transmit($payload);
} catch (ValidationException $e) {
foreach ($e->errors as $err) {
echo $err['loc'] . ': ' . $err['msg'] . PHP_EOL;
}
} catch (RateLimitException $e) {
echo "Slow down, retry in {$e->retryAfter}s" . PHP_EOL;
} catch (ZutaxException $e) {
echo "Failed: {$e->statusCode} {$e->getMessage()}" . PHP_EOL;
}

| Exception | When it fires | |---|---| | AuthException | 401 / 403 | | NotFoundException | 404 | | ValidationException | 400 / 422 | | ConflictException | 409 (idempotency-key reuse with mismatched payload) | | RateLimitException | 429 (SDK already retried, gave up) | | ServerException | 5xx (SDK already retried, gave up) | | ZutaxException | base class |

Pass a key per call:

$client->invoices->transmit($payload, idempotencyKey: 'INV-2026-0042');

…or scope a key to a callable when several calls share one logical operation:

use Zutax\IdempotencyKey;
IdempotencyKey::scoped('INV-2026-0042', function () use ($client, $payload) {
return $client->invoices->transmit($payload);
});
$client->invoices // transmit, get, getByExternalId, list, cancel
$client->parties // create, get, getByExternalId, list
$client->products // create, get, getByExternalId, list
$client->creditNotes // create, get, getByExternalId, list

A complete end-to-end example — read pending invoices from MySQL, transmit each one with an idempotency key, mark transmitted on success — lives in zutax-sdks/reference-connectors/php-mysql-source/. Fork it as a starting point for your own custom-ERP integration.

The SDK is open source: Zulaiy/zutax/zutax-sdks/php/.