The official Python 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 Python.
- Free to start —
inbio.shorten()and the QR API need no account and no API key - Zero dependencies — pure standard library, full type hints, Python ≥ 3.9
- Complete — covers the entire INBIO REST API: links CRUD, generator pagination, bulk create, QR codes, analytics, webhook signature verification
pip install inbioimport inbio
print(inbio.shorten("https://example.com/very/long/url").short_url)
# https://in.bio/x7Kp2qKeyless links are anonymous, rate-limited (5/min per IP), and deleted after
30 days unless claimed — the result's claim_url lets you keep one.
Create a token under Settings → API tokens (API access requires a Pro or Business plan), then:
from inbio import Inbio
client = Inbio("YOUR_TOKEN")
link = client.links.create("https://example.com/sale", slug="spring-sale")
print(link.short_url) # https://in.bio/spring-sale
print(link.total_clicks) # 0page = client.links.list(status="active", per_page=50)
print(page.total, page.current_page, page.has_next)
for link in page:
print(link.slug, link.destination_url)
# Auto-pagination: a generator that walks every page for you
for link in client.links.iterate(tag="marketing"):
print(link.short_url)link = client.links.create(
"https://example.com/launch",
slug="launch",
title="Product launch",
redirect_type=301,
folder_id=3,
tags=["marketing", "q3"],
expires_at="2026-12-31T23:59:59+00:00", # Pro+
fallback_url="https://example.com", # shown after expiry, Pro+
click_limit=10_000, # Pro+
password="s3cret", # Pro+
utm={"source": "newsletter", "medium": "email", "campaign": "launch"},
)link = client.links.update(42, title="New title", tags=["archive"])
link = client.links.disable(42) # active -> disabled
link = client.links.enable(42) # disabled -> active
client.links.delete(42) # soft-delete, stops redirectingEnable/disable only toggle between active and disabled; any other status
raises StateConflictError with the link's current status.
result = client.links.bulk_create([
{"destination_url": "https://example.com/a", "slug": "a1"},
{"destination_url": "https://example.com/b", "slug": "a1"}, # duplicate slug
])
for link in result.created:
print("created", link.slug)
for failure in result.failed:
print("row", failure.index, "failed:", failure.error)png = client.links.qr(42, format="png", size=512)
with open("spring-sale.png", "wb") as fh:
fh.write(png)
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-06-30")
print(stats.totals.clicks, stats.totals.uniques, stats.totals.bot_clicks)
for point in stats.series:
print(point["date"], point["clicks"])
for country in stats.countries: # top 10 by clicks
print(country["value"], country["clicks"])
# also: stats.devices, stats.browsers, stats.referrers, stats.range.from_/.tofor folder in client.folders.list():
print(folder.id, folder.name, folder.links_count)
for tag in client.tags.list():
print(tag.name, tag.links_count)usage = client.account.usage()
print(usage.plan) # "pro"
print(usage.usage["links_created"]) # 120
print(usage.limits["links_per_month"]) # 2000Verify the X-Inbio-Signature header against the exact raw request body
(configure endpoints under Settings → Webhooks; Business plan). Example
Flask handler:
from flask import Flask, request, abort
from inbio import Inbio, SignatureVerificationError
app = Flask(__name__)
client = Inbio()
@app.post("/webhooks/inbio")
def inbio_webhook():
try:
event = client.webhooks.verify(
request.get_data(), # raw bytes, not parsed JSON
request.headers.get("X-Inbio-Signature"),
"whsec_YOUR_SIGNING_SECRET",
tolerance=300, # reject stale timestamps
)
except SignatureVerificationError:
abort(400)
if event["event"] == "link.clicked":
print("click on", event["data"]["slug"])
return "", 204verify recomputes HMAC-SHA256(secret, "<t>.<raw body>"), compares in
constant time, and rejects timestamps older than tolerance seconds.
webhooks.construct_event(...) is an alias.
Every SDK error derives from inbio.InbioError (with .message, .status,
.error_type, .body).
| Exception | HTTP | Extra attributes |
|---|---|---|
AuthenticationError |
401 | |
AccessError |
403 | error_type = plan / scope / account; required scope |
NotFoundError |
404 | |
StateConflictError |
409 | current link status |
ValidationError |
422 | errors — per-field messages |
EntitlementError |
422 (error.type=entitlement) |
plan feature/limit |
RateLimitError |
429 | retry_after seconds |
ServerError |
5xx | |
SignatureVerificationError |
— | webhook verification failed |
from inbio import Inbio, RateLimitError, ValidationError
try:
client.links.create("not-a-url")
except ValidationError as err:
print(err.errors) # {"destination_url": ["The destination url ..."]}
except RateLimitError as err:
print(err.retry_after) # seconds to waitclient = Inbio(
"YOUR_TOKEN",
base_url="https://in.bio", # default
timeout=30, # seconds per request, default 30
max_retries=2, # default 2
)Retries apply only to idempotent GET requests (connection errors and 5xx) and
to 429 responses that carry Retry-After, with exponential backoff capped at
10 seconds.
# export INBIO_API_TOKEN=YOUR_TOKEN
from inbio import Inbio
client = Inbio() # picks up INBIO_API_TOKENINBIO (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.