# Zactonz PHP SDK

> PHP client for the Zactonz APIs: QR codes and barcodes, screenshots and PDFs, social card images, image conversion, link previews, page to Markdown, SSL, DNS, WHOIS, email checks and translation.

- **Type:** SDK (SDKs & tools)
- **Version:** 0.1.0, released 6 Oct 2026
- **Requires:** PHP 8.1+, curl and json extensions, No other dependencies
- **Licence:** MIT
- **Source:** https://github.com/zactonz/zactonz-php
- **Documentation:** https://developers.zactonz.com/tools/zactonz-php/

## Overview

The Zactonz PHP SDK wraps every public endpoint on `api.zactonz.com` in a typed PHP method. You pass arguments by name, read results as arrays or through a small helper, and let the client handle API keys, rate limits and retries.

```php
use Zactonz\Client;

$zactonz = new Client('zk_qr_your_key');

$qr = $zactonz->qr()->encode(content: 'https://zactonz.com', size: 6);
echo $qr['qr'];
```

### What you get

- **Named arguments for every endpoint**, with the API's own parameter descriptions in your editor. An argument you leave out is not sent, so the API applies its default.
- **Twenty-three methods on eleven services**, generated from the same OpenAPI specifications that produce this portal's reference pages, so they never drift from the API.
- **One key per product.** Pass all the keys you use and the client picks the right one for each call.
- **Exceptions for every failure**, including failures the API reports inside an HTTP 200 body.
- **Retries only where a retry cannot repeat work**, with the API's `Retry-After` honoured.
- **Raw bytes when you want them.** A method whose name ends in `File` returns the image or PDF instead of a link.
- **A fake transport** for your own test suite, so your code is tested without the network.

### Services

| Accessor | Methods | Key product |
|---|---|---|
| `qr()` | `encode`, `encodeFile`, `decode` | `qr` |
| `barcode()` | `encode`, `encodeFile` | `barcode` |
| `screenshot()` | `capture`, `captureFile`, `captureHtml` | `screen` |
| `og()` | `generate`, `generateFile` | `og` |
| `image()` | `convert`, `convertFile`, `info` | `image` |
| `links()` | `preview` | `unfurl` |
| `markdown()` | `fromUrl`, `fromHtml` | `markdown` |
| `domain()` | `ssl`, `dns`, `whois` | `domain` |
| `email()` | `health`, `verify` | `email`, `mverifier` |
| `translator()` | `translate` | `translator` |
| `drive()` | `directLink` | `gdrive` |

The [method reference](https://github.com/zactonz/zactonz-php/blob/main/docs/reference.md) on GitHub lists every method with its arguments. Each endpoint's response fields are documented on its page under [APIs](https://developers.zactonz.com/apis/).

### Requirements

PHP 8.1 or newer with the `curl` and `json` extensions. The SDK has no other dependencies. Supported PHP versions are those still receiving security fixes from the PHP project, and 8.1 for as long as it stays practical.

### Versioning

The library follows semantic versioning. While it is at `0.x`, a minor release may change the public API; such changes are listed in the [changelog](https://github.com/zactonz/zactonz-php/blob/main/CHANGELOG.md). The public API is everything documented here and in the method reference, except classes marked `@internal`.

## Installation

### Install with Composer

```bash
composer require zactonz/zactonz-php
```

The package is [zactonz/zactonz-php on Packagist](https://packagist.org/packages/zactonz/zactonz-php). Composer installs the current release under `vendor/zactonz/zactonz-php` and registers the `Zactonz` namespace with your autoloader. Nothing else is required; the library depends only on PHP 8.1 or newer with the `curl` and `json` extensions.

### Create API keys

Each Zactonz key works for one product, and the product is part of the key: `zk_qr_…` is a QR key, `zk_screen_…` a screenshot key. Create a key for each product you intend to call in the [API console](https://developers.zactonz.com/console/). There is a free plan.

Pass every key you have to the client. It picks the right one for each call and throws before any request is made if the key for a product is missing.

```php
$zactonz = new Client(['zk_qr_…', 'zk_screen_…', 'zk_domain_…']);
```

To keep keys out of your code, put them in the `ZACTONZ_API_KEYS` environment variable, separated by commas, and call `Client::fromEnvironment()`.

A value that is not a Zactonz key is rejected with `InvalidKeyException` rather than guessed at. Key values are hidden from `var_dump()` and stack traces. Keep them on the server; never ship them to a browser.

### Check a key

```php
if ($zactonz->hasKeyFor('screen')) {
    $left = $zactonz->keyStatus('screen')->get('quota.day.remaining');
}
```

`keyStatus()` calls the API's key check endpoint and returns the plan and the quota remaining today and this month.

## Usage

### Calling an endpoint

Every method takes named arguments that mirror the endpoint's parameters. Leave one out and it is not sent.

```php
$page    = $zactonz->markdown()->fromUrl(url: 'https://example.com/article', maxChars: 20000);
$records = $zactonz->domain()->dns(name: 'example.com', type: ['A', 'MX', 'TXT']);
$days    = $zactonz->domain()->ssl(host: 'example.com')['days_remaining'];
$checked = $zactonz->email()->verify(emails: ['jane@example.com', 'sales@example.com']);
```

### Reading results

Methods return a `Zactonz\Result`.

```php
$preview = $zactonz->links()->preview(url: 'https://zactonz.com');

$preview['title'];                   // a field
$preview->get('image.url');          // a nested field, null when absent
$preview->data;                      // the whole payload
$preview->rateLimit->remaining;      // requests left this minute
$preview->rateLimit->dayRemaining;   // units left today
$preview->requestId();               // for support requests
```

Most endpoints return an object whose fields you read directly. Three return a single value, available as `$result->data`: the `screenshot()` methods return a link, with `width` and `height` readable as fields; `qr()->decode()` returns the decoded text; `drive()->directLink()` returns a URL.

### Files

A method whose name ends in `File` returns the bytes as a `Zactonz\File` rather than a link.

```php
$zactonz->barcode()->encodeFile(content: 'ZCTZ-0042')->save('label.png');

$card = $zactonz->og()->generateFile(title: 'Ship faster', theme: 'dark');
$card->contentType;   // image/png
$card->dataUri();     // for an <img src>
```

`download()` fetches a link that came back in a result, and only ever over HTTP or HTTPS:

```php
$pdf = $zactonz->screenshot()->captureHtml(html: $invoiceHtml, format: 'pdf');
$zactonz->download($pdf->data)->save('invoice.pdf');
```

To upload a local image, pass its path:

```php
$zactonz->image()->convertFile(file: 'photo.jpg', format: 'webp', width: 800)->save('photo.webp');
```

Links to generated files are public to anyone who has them and expire. How long each lasts is on the endpoint's reference page.

### Signed image URLs

An `og:image` tag cannot send an `Authorization` header, so the [OG Image](https://developers.zactonz.com/apis/og-image/) endpoint also accepts a signed URL. `SignedUrl::og()` builds one from the key id and signing secret shown in the console. No request is made until something fetches the image.

```php
use Zactonz\SignedUrl;

$url = SignedUrl::og($keyId, $secret, [
    'title'    => $post->title,
    'subtitle' => $post->excerpt,
    'theme'    => 'dark',
]);
```

### Testing your code

`Zactonz\Testing\FakeTransport` replaces the network. Queue the responses your code should receive and inspect the requests it made.

```php
use Zactonz\Client;
use Zactonz\Testing\FakeTransport;

$transport = new FakeTransport(
    FakeTransport::json(['status' => 200, 'data' => ['qr' => 'https://api.zactonz.com/qr/enc/i/x.png']]),
);
$zactonz = new Client('zk_qr_test', maxRetries: 0, transport: $transport);

$zactonz->qr()->encode(content: 'hello');

$transport->lastRequest()->url;   // https://api.zactonz.com/qr/enc/?content=hello&resp=json
```

### Your own HTTP client

Requests go through `Zactonz\Http\Transport`, a one-method interface. Implement it to route requests through your own HTTP client, proxy or logging, and pass it as `transport`. An implementation must not follow redirects.

## Errors & retries

### Exceptions

| Exception | Meaning |
|---|---|
| `AuthenticationException` | The key is missing, unknown or expired. |
| `PermissionException` | The key belongs to another product, or the account is suspended. |
| `InvalidRequestException` | The request was understood and refused: a missing argument, an unreachable URL, a file that is too large. |
| `RateLimitException` | A rate limit or quota was reached. `$e->retryAfter` holds the seconds to wait, when known. |
| `ServerException` | The API or a service it depends on failed. |
| `UnexpectedResponseException` | The response was not what the endpoint documents. |
| `ConnectionException` | The API could not be reached. Nothing was sent. |
| `TimeoutException` | No response arrived in time. The request may still have been processed. |
| `ConfigurationException` | A key is missing (`MissingKeyException`) or malformed (`InvalidKeyException`). |

The first six extend `ApiException`, which carries `status`, the decoded `body`, the response `headers` and `requestId()`. `ConnectionException` and `TimeoutException` extend `TransportException`. All of them extend `ZactonzException`. Passing an argument that cannot be sent, such as a path that does not exist, throws PHP's `InvalidArgumentException`.

```php
use Zactonz\Exception\ApiException;
use Zactonz\Exception\RateLimitException;

try {
    $shot = $zactonz->screenshot()->capture(url: 'https://example.com');
} catch (RateLimitException $e) {
    $retryIn = $e->retryAfter;
} catch (ApiException $e) {
    error_log(sprintf('%d %s (%s)', $e->status, $e->getMessage(), $e->requestId()));
}
```

Some endpoints report a problem with the input using status `401` or `403` in the reply body, for example a QR image that cannot be loaded. The SDK raises those as `InvalidRequestException`; `AuthenticationException` and `PermissionException` are reserved for the key itself. A body whose `status` is not a number is treated as a failure, never as success.

### Retries

A request is sent again, up to `maxRetries` times (default 2), only when doing so cannot repeat work:

- after a `429`, once the `Retry-After` period has passed;
- after a `502`, `503` or `504`, for `GET` requests only;
- when the connection could not be made;
- once, ten seconds later, when the API host's request burst guard rejected the call. That guard answers with an HTML `403` when one address sends more than 20 requests to one endpoint within two seconds; see [rate limits](https://developers.zactonz.com/apis/rate-limits/).

One call waits at most 30 seconds in total between attempts. A longer `Retry-After`, such as a spent daily quota, is thrown as `RateLimitException` without waiting. Timeouts are never retried, because the request may have been processed.

### Timeouts

The default timeout is 90 seconds, because rendering a page or verifying an address can take most of a minute. Both the timeout and the retry count are constructor arguments:

```php
$zactonz = new Client($keys, timeout: 30, maxRetries: 0);
```

### Reporting a problem

Bugs and feature requests go to the [issue tracker](https://github.com/zactonz/zactonz-php/issues). Report a vulnerability privately by email as described in the repository's [security policy](https://github.com/zactonz/zactonz-php/blob/main/SECURITY.md); do not open a public issue for it.
