Official PHP SDK for the Rerout API.
Branded link infrastructure on Cloudflare — create short links, render QR codes, read analytics, and verify webhook signatures.
composer require rerout/sdkRequires PHP 8.2+ and the json and hash extensions. HTTP transport is
Guzzle 7.
use Rerout\Rerout;
use Rerout\Models\CreateLinkInput;
$rerout = new Rerout(getenv('REROUT_API_KEY'));
$link = $rerout->links()->create(new CreateLinkInput(
targetUrl: 'https://example.com/q4-sale',
domainHostname: 'go.brand.com',
code: 'q4',
));
echo $link->shortUrl; // https://go.brand.com/q4
$stats = $rerout->project()->stats(7);
echo "Last 7 days: {$stats->totalClicks} clicks, {$stats->qrScans} QR scans";$rerout = new Rerout('rrk_…', [
'base_url' => 'https://api.rerout.co', // optional, default shown
'timeout' => 30, // optional, seconds
'client' => $guzzleClient, // optional — inject your own ClientInterface
'default_headers' => [ // optional — added to every request
'User-Agent' => 'my-app/1.0',
],
]);A blank or missing API key throws a ReroutException with code
missing_api_key. The base_url has trailing slashes trimmed.
use Rerout\Models\CreateLinkInput;
use Rerout\Models\UpdateLinkInput;
$rerout->links()->create(new CreateLinkInput(
targetUrl: 'https://example.com',
domainHostname: 'go.brand.com', // optional
code: 'promo', // optional
expiresAt: 1893456000, // optional, unix seconds
seoTitle: 'Big Sale', // optional SEO overrides
));
$result = $rerout->links()->list(cursor: null, limit: 50);
foreach ($result->links as $link) {
echo $link->shortUrl, PHP_EOL;
}
// $result->nextCursor — pass back as `cursor` for the next page
$link = $rerout->links()->get('promo');
$link = $rerout->links()->update('promo', new UpdateLinkInput(
isActive: false,
));
$deleted = $rerout->links()->delete('promo'); // bool
$stats = $rerout->links()->stats('promo', days: 30);UpdateLinkInput distinguishes three states per field:
- Leave alone — omit the argument (the default). The field is not sent.
- Set a value — pass a concrete value.
- Clear server-side — pass
UpdateLinkInput::CLEAR, which serialises as explicitnull.
new UpdateLinkInput(
targetUrl: 'https://example.com/v2', // set
expiresAt: UpdateLinkInput::CLEAR, // null it on the server
// seoTitle omitted — left untouched
);Constructing an empty UpdateLinkInput and calling update() throws a
ReroutException (code bad_request) client-side — the request never leaves
your process.
$stats = $rerout->project()->stats(days: 30);
echo $stats->totalClicks, ' ', $stats->qrScans;
$me = $rerout->project()->me(); // array{id: string, name: string, slug: string}use Rerout\Models\QrOptions;
// Pure URL builder — no network call.
$url = $rerout->qr()->url('promo', new QrOptions(
size: 12,
margin: 2,
ecc: 'H',
domain: 'go.brand.com',
refresh: true, // true → `refresh=1`; any string is forwarded verbatim
));
// Fetch the rendered SVG (sends the bearer token).
$svg = $rerout->qr()->svg('promo', new QrOptions(size: 8));Rerout signs every webhook delivery with an X-Rerout-Signature header in the
form t=<unix>,v1=<hex_hmac_sha256>. Verify it before trusting the payload:
use Rerout\Webhooks\SignatureVerifier;
$ok = SignatureVerifier::verify(
rawBody: file_get_contents('php://input'),
signatureHeader: $_SERVER['HTTP_X_REROUT_SIGNATURE'] ?? '',
secret: getenv('REROUT_WEBHOOK_SECRET'),
);
if (!$ok) {
http_response_code(401);
exit;
}The HMAC is SHA-256 over "<timestamp>.<raw_body>" and is compared in constant
time. The default timestamp tolerance is 300 seconds — pass
toleranceSeconds: 0 to disable the staleness check, or a custom value to
widen it. A $clock callable can be injected for testing.
Every API call throws Rerout\Exceptions\ReroutException on failure:
use Rerout\Exceptions\ReroutException;
try {
$rerout->links()->create(new CreateLinkInput(targetUrl: 'http://insecure'));
} catch (ReroutException $e) {
echo $e->code(); // 'bad_target_url'
echo $e->status(); // 400
echo $e->getMessage(); // 'target_url must use https.'
echo $e->path ?? ''; // the API path that failed
if ($e->isRateLimited()) { /* back off */ }
if ($e->isServerError()) { /* retry later */ }
}When the server returns a JSON error body, code and message are taken
verbatim. When it does not, a synthetic code is used:
| Condition | Code |
|---|---|
| HTTP 401 | unauthorized |
| HTTP 403 | forbidden |
| HTTP 404 | not_found |
| HTTP 429 | rate_limited |
| HTTP 5xx | server_error |
| other 4xx | client_error |
| connection failure | network_error |
| timeout | timeout |
| 2xx non-JSON body | unexpected_response |
composer install
composer test # PHPUnit
composer analyse # PHPStan, level 9
composer format # php-cs-fixerMIT — see LICENSE.