SHL-001
Shortlink providers are an API call, not a URL pattern
Every provider shortens by API: you POST or GET their endpoint with your token and a destination, and they return JSON containing the short URL. Gluing a destination onto a base URL works with none of them.
The tempting integration is a template — https://provider.example/st/?api=KEY&url= with the destination appended. It is in a lot of tutorials and it is wrong for essentially every provider in use.
What they actually offer is an API: you call their endpoint with your token and the destination, and they answer with JSON containing a short URL that you then send the user to. The difference matters because the short URL is created server-side and tracked; a hand-glued link is not a link they know about, so completions never register and the user earns nothing.
Across a list of a hundred or so providers the contract is near-identical, which is what makes one client cover all of them: token, url, optional alias, JSON back.
/**
* One client for every provider that speaks the common contract:
* GET {endpoint}?api={token}&url={destination}[&alias={alias}]
* -> {"status":"success","shortenedUrl":"https://.../abc"}
*/
function shorten(array $provider, string $destination, string $alias = ''): ?string
{
$query = [
$provider['token_param'] ?: 'api' => $provider['token'],
$provider['url_param'] ?: 'url' => $destination,
];
if ($alias !== '') {
$query['alias'] = $alias;
}
$endpoint = rtrim((string) $provider['endpoint'], '?&');
$url = $endpoint . (str_contains($endpoint, '?') ? '&' : '?') . http_build_query($query);
$body = fetch_guarded($url, 12);
if ($body === null) {
error_log('shortlink: provider ' . $provider['id'] . ' did not answer');
return null;
}
$data = json_decode($body, true);
if (!is_array($data)) {
// Some return bare text when format=text is requested.
$trimmed = trim($body);
return preg_match('~^https?://~i', $trimmed) ? $trimmed : null;
}
// Field name varies a little; these cover the common contract.
foreach (['shortenedUrl', 'short', 'shortened_url', 'url', 'result'] as $key) {
if (!empty($data[$key]) && is_string($data[$key])) {
return $data[$key];
}
}
error_log('shortlink: ' . $provider['id'] . ' said ' . substr($body, 0, 200));
return null;
}
Using it
Store the endpoint and the parameter names per provider, not a URL template. That is what lets you add a provider from an admin form instead of a deploy.
Call the API when the user starts a task, not in advance. A pre-generated pool goes stale and wastes their quota.
If a provider genuinely only offers a template, treat it as the exception and record why in the provider row.
What bites people
A failed shorten must not consume the user's daily attempt. Fail the task cleanly and let them try again.
Never echo the request URL into an error message shown to the user — it contains your API token. Log it server-side and show something generic.
Some providers rate limit per minute. Spread retries rather than hammering, or the whole site's links stop working at once.
This one touches real money. Point it at a throwaway wallet and watch a full cycle before you trust it with a live balance.