The official PHP SDK for INBIO, the premium URL shortener with click analytics and customizable QR codes. Shorten links, generate styled QR codes, read analytics, and manage folders and tags from PHP.
- Free to start —
Inbio::shorten()and the QR API need no account and no API key - Zero Composer dependencies — PHP ≥ 8.1 with
ext-curl; readonly typed models, named arguments - Complete — covers the entire INBIO REST API: links CRUD, generator pagination, bulk create, QR codes, analytics, webhook signature verification
composer require inbio/inbioNo account, no API key:
$result = \Inbio\Inbio::shorten('https://example.com/some/very/long/url');
echo $result->shortUrl; // https://in.bio/abc123shorten() returns a ShortenResult with shortUrl, slug, qrUrl,
previewUrl, claimUrl and expires. Keyless links are anonymous and
deleted after 30 days unless claimed via claimUrl.
Create a token under Settings → API tokens (API access is a Pro/Business feature), then:
use Inbio\Client;
$client = new Client(token: 'YOUR_API_TOKEN');
$link = $client->links->create('https://example.com/sale', slug: 'spring-sale');
echo $link->shortUrl; // https://in.bio/spring-saleOmit the token to fall back to the INBIO_API_TOKEN environment variable:
$client = new Client(); // reads INBIO_API_TOKEN// One page at a time
$page = $client->links->list(status: 'active', perPage: 50);
foreach ($page->data as $link) {
echo "{$link->slug}: {$link->totalClicks} clicks\n";
}
echo $page->total; // total matching links
echo $page->currentPage; // current page number
var_dump($page->hasNext); // is there a next page?
// Or let the SDK paginate for you — iterate() is a Generator
foreach ($client->links->iterate(tag: 'marketing') as $link) {
echo $link->shortUrl, "\n";
}listAll() is an alias of iterate(). Filters: search, folderId,
tag, status, perPage.
$link = $client->links->create(
'https://example.com/sale',
slug: 'spring-sale', // omit for a random slug
title: 'Spring sale',
redirectType: 301, // 301, 302 (default) or 307
folderId: 3,
tags: ['marketing', 'q3'], // created on the fly
expiresAt: '2026-09-01T00:00:00+00:00', // Pro+
fallbackUrl: 'https://example.com', // shown after expiry; Pro+
clickLimit: 10000, // Pro+
password: 'hunter2', // Pro+
utm: ['source' => 'newsletter', 'campaign' => 'spring'],
);$client->links->update(42, title: 'New title', tags: ['sale']);
// or with an array: $client->links->update(42, ['title' => 'New title']);
$client->links->disable(42); // active -> disabled
$client->links->enable(42); // disabled -> active
$client->links->delete(42); // soft-delete, stops redirectingEnable/disable only toggle between active and disabled; any other
status throws a StateConflictException carrying the current status.
$result = $client->links->bulkCreate([
['destination_url' => 'https://example.com/1', 'slug' => 'promo-1'],
['destination_url' => 'https://example.com/2', 'slug' => 'promo-2'],
]);
foreach ($result->created as $link) { /* Link objects */ }
foreach ($result->failed as $row) {
echo "row {$row['index']} failed: {$row['error']}\n";
}Rows fail independently — up to 100 per call.
file_put_contents('spring-sale.png', $client->links->qr(42, size: 512));
file_put_contents('spring-sale.svg', $client->links->qr(42, format: 'svg'));The QR encodes the short URL, so editing the destination never invalidates printed codes.
$stats = $client->links->analytics(42, from: '2026-06-01', to: '2026-07-01');
echo $stats->totals['clicks']; // 1240
echo $stats->totals['uniques']; // 981
foreach ($stats->series as $day) {
echo "{$day['date']}: {$day['clicks']}\n";
}
// Also: $stats->countries, ->devices, ->browsers, ->referrers (top 10 by clicks)foreach ($client->folders->list() as $folder) {
echo "{$folder->name} ({$folder->linksCount} links)\n";
}
foreach ($client->tags->list() as $tag) {
echo "{$tag->name}\n";
}$usage = $client->account->usage();
echo $usage->plan; // "pro"
echo $usage->usage['links_created']; // 120
echo $usage->limits['links_per_month']; // 2000in.bio signs every webhook delivery with
X-Inbio-Signature: t=<unix>,v1=<hex>. Verify with the raw request body —
do not decode and re-encode it first:
use Inbio\Exception\SignatureVerificationException;
use Inbio\Resources\Webhooks;
// In your HTTP handler (plain PHP shown; the same works in any framework):
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_INBIO_SIGNATURE'] ?? '';
try {
$event = (new Webhooks())->verify($payload, $signature, $endpointSecret);
// or: $client->webhooks->verify(...)
} catch (SignatureVerificationException $e) {
http_response_code(400);
exit;
}
match ($event->event) {
'link.created' => handleCreated($event->data),
'link.click_limit_reached' => notifyTeam($event->data),
default => null,
};
http_response_code(200);Verification uses hash_hmac('sha256', ...) with a constant-time
comparison and rejects timestamps older than 5 minutes (configurable via
the tolerance parameter) to prevent replay attacks.
constructEvent() is an alias of verify().
All exceptions extend Inbio\Exception\InbioException, which exposes
status, errorType and responseBody:
use Inbio\Exception\RateLimitException;
use Inbio\Exception\ValidationException;
try {
$client->links->create('https://example.com', slug: 'taken');
} catch (ValidationException $e) {
print_r($e->errors); // ['slug' => ['This slug is already taken.']]
} catch (RateLimitException $e) {
sleep($e->retryAfter ?? 60);
}Exception (in Inbio\Exception) |
HTTP | Extra fields |
|---|---|---|
AuthenticationException |
401 | |
AccessException |
403 | errorType = plan/scope/account, required scope |
NotFoundException |
404 | |
StateConflictException |
409 | current status |
ValidationException |
422 | errors map (field => messages) |
EntitlementException |
422 (error.type=entitlement) |
plan feature/limit |
RateLimitException |
429 | retryAfter seconds |
ServerException |
5xx | |
SignatureVerificationException |
— | webhook verification failure |
$client = new Client(
token: 'YOUR_API_TOKEN',
baseUrl: 'https://in.bio', // default
timeout: 30.0, // seconds, default 30
maxRetries: 2, // default 2
);The client retries only idempotent GET requests (on network errors and
5xx) and 429 responses that carry a Retry-After header, with
exponential backoff capped at 10 seconds. Non-idempotent requests are
never retried automatically.
Set INBIO_API_TOKEN and construct the client without arguments — handy
for CI and twelve-factor apps:
export INBIO_API_TOKEN=inbio_pat_...$client = new \Inbio\Client();Full API reference: https://docs.in.bio
INBIO (in.bio) is a URL shortener and link-management
platform: short links with custom slugs, real-time click analytics
(countries, devices, browsers, referrers — bots filtered out), a QR code
studio with dot styles, marker shapes and colors, folders, tags, UTM
tools, and a REST API with webhooks. Free plan included.
- Website: https://in.bio
- Documentation: https://docs.in.bio
- Free shorten API (no key): https://docs.in.bio/api/free-shorten
- Free QR code API (no key): https://docs.in.bio/api/free-qr
- MCP server for AI agents: https://docs.in.bio/api/mcp
- All SDKs (JavaScript, Python, PHP, Ruby, Go): https://docs.in.bio/sdks
MIT © InBio, Inc.