Metadata-Version: 2.4
Name: inbio
Version: 0.1.0
Summary: Official Python SDK for the in.bio URL shortener API: short links, QR codes, analytics, webhooks.
Project-URL: Homepage, https://in.bio
Project-URL: Repository, https://in.bio
Project-URL: Documentation, https://docs.in.bio
Author: INBIO
License: MIT
License-File: LICENSE
Keywords: analytics,in.bio,qr codes,short links,url shortener
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.9
Description-Content-Type: text/markdown

# inbio — official Python SDK for INBIO (in.bio)

The official Python SDK for [INBIO](https://in.bio), 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](https://docs.in.bio/api/free-qr) 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](https://docs.in.bio/api): links CRUD, generator pagination, bulk create, QR codes, analytics, webhook signature verification

## Install

```bash
pip install inbio
```

## Shorten a URL — no account, no API key

```python
import inbio

print(inbio.shorten("https://example.com/very/long/url").short_url)
# https://in.bio/x7Kp2q
```

Keyless 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.

## Authenticated quickstart

Create a token under **Settings → API tokens** (API access requires a Pro or
Business plan), then:

```python
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)   # 0
```

## List and iterate links

```python
page = 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)
```

## Create with options

```python
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"},
)
```

## Update, enable, disable, delete

```python
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 redirecting
```

Enable/disable only toggle between `active` and `disabled`; any other status
raises `StateConflictError` with the link's `current` status.

## Bulk create (Business)

```python
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)
```

## QR code to a file

```python
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.

## Analytics

```python
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_/.to
```

## Folders and tags

```python
for 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)
```

## Account usage

```python
usage = client.account.usage()
print(usage.plan)                          # "pro"
print(usage.usage["links_created"])        # 120
print(usage.limits["links_per_month"])     # 2000
```

## Webhook verification

Verify the `X-Inbio-Signature` header against the **exact raw request body**
(configure endpoints under **Settings → Webhooks**; Business plan). Example
Flask handler:

```python
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 "", 204
```

`verify` recomputes `HMAC-SHA256(secret, "<t>.<raw body>")`, compares in
constant time, and rejects timestamps older than `tolerance` seconds.
`webhooks.construct_event(...)` is an alias.

## Error handling

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 |

```python
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 wait
```

## Retries and timeouts

```python
client = 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.

## Authenticating via environment variable

```python
# export INBIO_API_TOKEN=YOUR_TOKEN
from inbio import Inbio

client = Inbio()  # picks up INBIO_API_TOKEN
```

## About INBIO

[INBIO](https://in.bio) (`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

## License

MIT © [InBio, Inc.](https://in.bio)
