For bots, scripts and anything that speaks http.
Nothing is stored. A short link is the original link, compressed
into the address itself, so shorten and expand are
pure functions of their input. No database, no accounts, no rate limit, no
expiry and nothing to leak.
Links come in two flavours: #fragment links, which are unpacked
in your browser, and /path links (mode=qr), which
the site reads from the path and which are therefore safe to print as a QR
code.
| Method | Path | Description |
|---|---|---|
GET, POST |
/api/v1/shorten |
Compress a link. Parameters: url (required), mode = hash, emoji or qr. |
GET |
/api/v1/expand |
Unpack a short link. Parameters: link (a full short link, or a bare payload), mode to disambiguate a bare payload. |
GET |
/api/v1/health |
Liveness check. |
GET |
/api/v1 |
This list, as JSON. |
# json in, json out, which is what curl gets
curl -G "https://zbi.baby/api/v1/shorten" \
--data-urlencode "url=https://www.amazon.com/dp/B0CX23V2ZK/ref=sr_1_1?keywords=widget&qid=1720000000"
# the --data-urlencode is what keeps the ? and & in the link from being read
# as parameters of the api call itself
# one line of text instead of json, for bots
curl -G "https://zbi.baby/api/v1/shorten" --data-urlencode "url=https://example.com/x" -d "format=text"
curl -H "Accept: text/plain" "https://zbi.baby/api/v1/shorten?url=https://example.com/x"
# post it instead, as json
curl -X POST https://zbi.baby/api/v1/shorten \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com/some/long/path","mode":"hash"}'
# or as a form, the way a html form would send it
curl -X POST https://zbi.baby/api/v1/shorten -d "url=https://example.com/x"
# qr links keep the payload in the path
curl -G "https://zbi.baby/api/v1/shorten" --data-urlencode "url=https://example.com/x" -d "mode=qr" -d "format=text"
Response, for the amazon link above:
{
"input": "https://www.amazon.com/dp/B0CX23V2ZK/ref=sr_1_1?keywords=widget&qid=1720000000",
"short": "https://zbi.baby#j,7*+lgC#nlHXCWK7#Bs!+ZxT*GtbviRyo_Zl@C*/?f@IA$0[wM'Tj&!",
"payload": "j,7*+lgC#nlHXCWK7#Bs!+ZxT*GtbviRyo_Zl@C*/?f@IA$0[wM'Tj&!",
"mode": "hash",
"input_length": 78,
"payload_length": 56,
"short_length": 74,
"shorter_percent": 5
}
# percent-encode the # so it is not read as a fragment of the api call curl "https://zbi.baby/api/v1/expand?link=https%3A%2F%2Fzbi.baby%23j%2C7*%2BlgC%23nlHXCWK7%23Bs!%2BZxT*GtbviRyo_Zl%40C*%2F%3Ff%40IA$0%5BwM%27Tj%26%21" # or let curl do it curl -G "https://zbi.baby/api/v1/expand" \ --data-urlencode "link=https://zbi.baby#j,7*+lgC#nlHXCWK7#Bs!+ZxT*GtbviRyo_Zl@C*/?f@IA$0[wM'Tj&!" # a bare payload works too curl -G "https://zbi.baby/api/v1/expand" --data-urlencode "payload=OQ/ap#"
A bare qr payload is the one case that needs a hint, because
those symbols are indistinguishable from a text payload, and decoding one as
the other can still come out looking like a link. Send mode=qr,
or put a / in front of the payload, or just send the whole short
link. Guessing would mean handing back the wrong link with a 200, so the api
answers 400 ambiguous_payload instead.
Response:
{ "url": "https://www.amazon.com/dp/B0CX23V2ZK/ref=sr_1_1?keywords=widget&qid=1720000000" }
One GET is all it takes, and there is no key to register. Two things are worth knowing if the caller is a chat bot:
#fragment link is only decodable in a browser, because a
fragment never reaches a server. Anything that fetches a link itself — a
link preview crawler, a validator, a client that trims the tail off — sees
plain zbi.baby and nothing more. When you need a link that
stands on its own, ask for mode=qr: the payload rides in the
path, so the link is decodable by anything.
Accept: text/plain or format=text answers with
the link and nothing else, which drops straight into a message.
# discord.py
import requests
from discord.ext import commands
@commands.command()
async def shorten(ctx, url: str):
response = requests.get(
"https://zbi.baby/api/v1/shorten",
params={"url": url, "mode": "qr"},
headers={"Accept": "text/plain"},
timeout=5,
)
response.raise_for_status()
await ctx.send(response.text.strip())
// discord.js
const query = new URLSearchParams({ url, mode: "qr", format: "text" });
const response = await fetch(`https://zbi.baby/api/v1/shorten?${query}`);
if (response.ok) {
await interaction.reply((await response.text()).trim());
}
On Cloudflare, two settings will bite you: keep Bot Fight Mode off for
/api, because it challenges requests that look like bots and a
bot asking for a short link is exactly that; and check that the Worker route
really does win over Pages for /api, or requests will land on
the static site and come back as HTML instead of JSON.
Anything that goes wrong answers with a matching status and a code:
{
"error": {
"code": "unsupported_protocol",
"message": "Only http and https links can be compressed, got \"ftp:\"."
}
}
400 missing_url, invalid_url, unsupported_protocol, credentials_not_supported, unknown_mode, missing_link, undecodable, ambiguous_payload, invalid_json404 not_found405 method_not_allowed413 body_too_long414 url_too_long415 unsupported_media_typehttp and https links, and no credentials in the URLMAX_URL_LENGTHformat=text or Accept: text/plain returns bare text instead of JSONSource code · zbi.baby · fork of ha.mr