Dataclass models, full type hints, zero dependencies (pure standard library). Python ≥ 3.9.
pip install inbioShorten a URL (no account)
import inbio
link = inbio.shorten("https://example.com/very/long/url")
print(link.short_url) # https://in.bio/a8K2pAuthenticated client
Pass a token or set INBIO_API_TOKEN in the environment:
from inbio import Inbio
client = Inbio() # reads INBIO_API_TOKEN
created = client.links.create(
"https://example.com/sale",
slug="spring-sale",
tags=["marketing"],
utm={"source": "newsletter", "medium": "email"},
)Everything else
# List with filters, or iterate every page automatically
page = client.links.list(status="active", per_page=50)
for link in client.links.iterate(tag="marketing"):
print(link.slug, link.total_clicks)
# Update, toggle, delete
client.links.update(42, title="Renamed")
client.links.disable(42)
client.links.enable(42)
client.links.delete(42)
# QR code bytes → file
qr = client.links.qr(42, format="png", size=1024)
open("qr.png", "wb").write(qr)
# Analytics, folders, tags, usage (from is a Python keyword → from_)
stats = client.links.analytics(42, from_="2026-06-01")
folders = client.folders.list()
tags = client.tags.list()
usage = client.account.usage()Errors
from inbio import RateLimitError, ValidationError
try:
client.links.create(url)
except ValidationError as e:
print(e.errors) # per-field messages
except RateLimitError as e:
print(e.retry_after) # secondsAuthenticationError (401), AccessError (403, plan or scope),
NotFoundError, ValidationError, EntitlementError, StateConflictError
(409), RateLimitError (429), ServerError — all subclass InbioError.
GETs and Retry-After 429s are retried automatically with backoff.
Webhooks
# e.g. in a Flask handler
event = client.webhooks.verify(
request.get_data(), # raw body bytes
request.headers["X-Inbio-Signature"],
secret,
)
if event["event"] == "link.clicked":
...Signatures are checked with hmac.compare_digest and a 5-minute
replay-protection window. See Webhooks for the payload format.