SDK for the shrtnr URL shortener API
Project description
shrtnr: URL Shortener SDK for Python
Python client for creating short links, managing URLs, and reading click analytics from a shrtnr instance. Ships synchronous and asynchronous clients sharing the same method surface.
Install
pip install shrtnr
Python 3.9 or newer. httpx is the only runtime dependency.
Quick Start
from shrtnr import Shrtnr, CreateLinkOptions
with Shrtnr("https://your-shrtnr.example.com", api_key="sk_your_api_key") as client:
link = client.create_link(CreateLinkOptions(
url="https://example.com/long-page",
label="Campaign landing page",
))
print(link) # Link(id=1, slugs=[Slug(slug='a3x', ...)], ...)
Async usage is identical, with await and an async with:
import asyncio
from shrtnr import AsyncShrtnr, CreateLinkOptions
async def main() -> None:
async with AsyncShrtnr("https://your-shrtnr.example.com", api_key="sk_your_api_key") as client:
link = await client.create_link(CreateLinkOptions(url="https://example.com"))
print(link)
asyncio.run(main())
What This SDK Covers
This package wraps the public link-management API:
- Shorten URLs (create short links)
- Add, disable, enable, and remove custom slugs
- List, read, update, disable, enable, and delete links
- List links by owner identity
- Read click analytics (referrer, country, device, browser)
- Group links into bundles and read combined engagement stats
- Check service health
Administrative operations (API key management, settings, dashboard stats) are not part of this package. Those are accessible through the admin UI.
API Reference
Every method below exists on both Shrtnr (sync) and AsyncShrtnr (async). Async examples are omitted for brevity but mirror the sync examples with await.
create_link
Shorten a URL. Returns a Link with a random slug.
import time
link = client.create_link(CreateLinkOptions(
url="https://example.com",
label="My link to the example page",
expires_at=int(time.time()) + 86400,
))
To add a custom slug, call add_custom_slug after creation:
link = client.create_link(CreateLinkOptions(url="https://example.com"))
slug = client.add_custom_slug(link.id, "my-campaign")
list_links
List all short links.
links = client.list_links()
get_link
Get a single link by ID, including its slugs and click count.
link = client.get_link(123)
get_link_by_slug
Get a single link by its short URL slug (including custom slugs).
link = client.get_link_by_slug("my-custom-slug")
update_link
Update a link's URL, label, or expiry. Omit fields to leave them unchanged; pass None explicitly to clear them on the server.
updated = client.update_link(123, UpdateLinkOptions(
label="Updated label",
expires_at=None,
))
disable_link
Disable a link so it stops redirecting.
disabled = client.disable_link(123)
enable_link
Re-enable a previously disabled link.
link = client.enable_link(123)
delete_link
Permanently delete a link. Only succeeds if the link has zero clicks — disable it instead if it has traffic.
client.delete_link(123)
list_links_by_owner
List all links created by a specific identity (typically an email address).
links = client.list_links_by_owner("user@example.com")
add_custom_slug
Add a custom short URL slug to an existing link. Raises ShrtnrError with status 409 if the slug already exists, or 400 for invalid format.
slug = client.add_custom_slug(123, "campaign")
disable_slug
Disable a custom slug without affecting the parent link or its other slugs.
client.disable_slug(123, "campaign")
enable_slug
Re-enable a disabled custom slug.
client.enable_slug(123, "campaign")
remove_slug
Permanently remove a custom slug. Only succeeds if the slug has zero clicks.
client.remove_slug(123, "campaign")
get_link_qr
Fetch the QR code SVG for a link as a string. Optionally specify which slug to encode.
svg = client.get_link_qr(123)
svg_for_slug = client.get_link_qr(123, slug="my-campaign")
get_link_analytics
Read click analytics for a link: referrer, country, device type, and browser breakdown.
analytics = client.get_link_analytics(123)
health
Check service health and version.
health = client.health()
create_bundle
Create a bundle to group related links. Returns the new Bundle.
bundle = client.create_bundle(CreateBundleOptions(
name="Spring campaign",
description="Email, social, and paid drops",
icon="sparkles",
accent="purple",
))
list_bundles
List bundles with summary stats: lifetime click total, 30-day sparkline, and top links. Archived bundles are hidden by default.
bundles = client.list_bundles()
with_archived = client.list_bundles(archived=True)
get_bundle
Fetch a single bundle's metadata by ID.
bundle = client.get_bundle(42)
update_bundle
Rename a bundle or change its description, icon, or accent.
updated = client.update_bundle(42, UpdateBundleOptions(
name="Spring 2026 campaign",
accent="green",
))
delete_bundle
Permanently delete a bundle. Member links are preserved, only the grouping is discarded.
client.delete_bundle(42)
archive_bundle
Archive a bundle so it drops out of the default list_bundles response. Member links keep working.
client.archive_bundle(42)
unarchive_bundle
Restore a previously archived bundle.
client.unarchive_bundle(42)
get_bundle_analytics
Read combined analytics across every link in the bundle: timeline, per-link breakdown, countries, devices, browsers. Pass a TimelineRange to set the window (default "30d").
stats = client.get_bundle_analytics(42, range="7d")
print(stats.total_clicks, stats.per_link)
list_bundle_links
List every link currently in a bundle.
links = client.list_bundle_links(42)
add_link_to_bundle
Attach a link to a bundle. Idempotent: re-adding an existing member is a no-op.
client.add_link_to_bundle(42, 123)
remove_link_from_bundle
Detach a link from a bundle. The link itself stays, only the membership is removed.
client.remove_link_from_bundle(42, 123)
list_bundles_for_link
List every bundle a given link belongs to.
bundles = client.list_bundles_for_link(123)
Error Handling
Non-2xx responses raise ShrtnrError with the status code, message, and raw response body.
from shrtnr import ShrtnrError
try:
client.get_link(99999)
except ShrtnrError as error:
print(error.status) # 404
print(str(error)) # "not found"
print(error.body)
Custom slug collisions and format errors from add_custom_slug raise ShrtnrError (status 409 or 400). Handle them per-call.
Differences from the TypeScript SDK
The Python SDK mirrors the TypeScript SDK method-for-method, with a few idiomatic adaptations:
- Sync + async clients.
Shrtnruseshttpx.Client;AsyncShrtnruseshttpx.AsyncClient. Surface is identical. - Naming.
snake_casethroughout (create_link,get_link_analytics), matching PEP 8. - Error class.
ShrtnrErrorsubclassesException. Catch withexcept ShrtnrError. - Timestamps. Unix seconds as
int, matching the wire format. Nodatetimeconversion. - Options.
CreateLinkOptions/UpdateLinkOptions/CreateBundleOptions/UpdateBundleOptionsare dataclasses.UpdateLinkOptionstreats omitted fields as "leave unchanged"; passNoneto clear on the server. - Return types. Frozen dataclasses for every response model. Full type hints;
mypy --strictclean.
License
Apache-2.0. See the root LICENSE file.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file shrtnr-0.1.0.tar.gz.
File metadata
- Download URL: shrtnr-0.1.0.tar.gz
- Upload date:
- Size: 15.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
eb505701a380febb3453fcae5774e5ec2051fb3dd20551b3d64473e79cb6c952
|
|
| MD5 |
21fa3eeb3f273a6369e8851b317331e4
|
|
| BLAKE2b-256 |
cb9546997314913223eccc6845e513b9254f34dccc505c1e803d200a7af546fd
|
Provenance
The following attestation bundles were made for shrtnr-0.1.0.tar.gz:
Publisher:
release-sdk-python.yml on oddbit/shrtnr
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
shrtnr-0.1.0.tar.gz -
Subject digest:
eb505701a380febb3453fcae5774e5ec2051fb3dd20551b3d64473e79cb6c952 - Sigstore transparency entry: 1361634706
- Sigstore integration time:
-
Permalink:
oddbit/shrtnr@5ad5478372e8e44c3e3e68e27e84f47925e30531 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/oddbit
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release-sdk-python.yml@5ad5478372e8e44c3e3e68e27e84f47925e30531 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file shrtnr-0.1.0-py3-none-any.whl.
File metadata
- Download URL: shrtnr-0.1.0-py3-none-any.whl
- Upload date:
- Size: 18.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0dc6753c114f6e91ed30c0540e4505663a45442fc303bca5d09c1b4db4ba77c3
|
|
| MD5 |
d42188f8b8409d8297ea31cb8a3e3d33
|
|
| BLAKE2b-256 |
b9da775cd016742d53fdfd66ced2014f81dceeb5e258a060b1ddeffd632c91a9
|
Provenance
The following attestation bundles were made for shrtnr-0.1.0-py3-none-any.whl:
Publisher:
release-sdk-python.yml on oddbit/shrtnr
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
shrtnr-0.1.0-py3-none-any.whl -
Subject digest:
0dc6753c114f6e91ed30c0540e4505663a45442fc303bca5d09c1b4db4ba77c3 - Sigstore transparency entry: 1361634723
- Sigstore integration time:
-
Permalink:
oddbit/shrtnr@5ad5478372e8e44c3e3e68e27e84f47925e30531 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/oddbit
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release-sdk-python.yml@5ad5478372e8e44c3e3e68e27e84f47925e30531 -
Trigger Event:
workflow_dispatch
-
Statement type: