Skip to main content

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. Shrtnr uses httpx.Client; AsyncShrtnr uses httpx.AsyncClient. Surface is identical.
  • Naming. snake_case throughout (create_link, get_link_analytics), matching PEP 8.
  • Error class. ShrtnrError subclasses Exception. Catch with except ShrtnrError.
  • Timestamps. Unix seconds as int, matching the wire format. No datetime conversion.
  • Options. CreateLinkOptions / UpdateLinkOptions / CreateBundleOptions / UpdateBundleOptions are dataclasses. UpdateLinkOptions treats omitted fields as "leave unchanged"; pass None to clear on the server.
  • Return types. Frozen dataclasses for every response model. Full type hints; mypy --strict clean.

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

shrtnr-0.1.0.tar.gz (15.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

shrtnr-0.1.0-py3-none-any.whl (18.1 kB view details)

Uploaded Python 3

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

Hashes for shrtnr-0.1.0.tar.gz
Algorithm Hash digest
SHA256 eb505701a380febb3453fcae5774e5ec2051fb3dd20551b3d64473e79cb6c952
MD5 21fa3eeb3f273a6369e8851b317331e4
BLAKE2b-256 cb9546997314913223eccc6845e513b9254f34dccc505c1e803d200a7af546fd

See more details on using hashes here.

Provenance

The following attestation bundles were made for shrtnr-0.1.0.tar.gz:

Publisher: release-sdk-python.yml on oddbit/shrtnr

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for shrtnr-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 0dc6753c114f6e91ed30c0540e4505663a45442fc303bca5d09c1b4db4ba77c3
MD5 d42188f8b8409d8297ea31cb8a3e3d33
BLAKE2b-256 b9da775cd016742d53fdfd66ced2014f81dceeb5e258a060b1ddeffd632c91a9

See more details on using hashes here.

Provenance

The following attestation bundles were made for shrtnr-0.1.0-py3-none-any.whl:

Publisher: release-sdk-python.yml on oddbit/shrtnr

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page