Skip to main content

Official Python SDK for the AWSYS.CO URL Shortener API

Project description

awsysco — Official Python SDK

PyPI version Python versions License: MIT

The official Python SDK for the AWSYS.CO URL Shortener API.

Installation

pip install awsysco

Requires Python 3.9+.

Quick Start

from awsysco import Client

client = Client(api_key="awsys_your_key_here")

# Shorten a URL
link = client.links.create("https://example.com/very/long/path")
print(link.short_url)  # https://awsys.co/abc123

# Get analytics
stats = client.analytics.get_stats(link.short_code)
print(f"Total clicks: {stats.total_clicks}")

# Build a QR code URL
qr_url = client.qr.get_url(link.short_code, size=400)
print(qr_url)

Authentication

Generate an API key from your AWSYS dashboard. All API keys begin with awsys_.

client = Client(api_key="awsys_...")

Store keys in environment variables — never hardcode them:

import os
from awsysco import Client

client = Client(api_key=os.environ["AWSYS_API_KEY"])

API Reference

Links

Method Description
client.links.create(url, *, custom_slug, expires_at, max_clicks) Create a shortened link
client.links.list(*, limit=20, offset=0) List links (paginated)
client.links.get(short_path) Get a single link
client.links.update(short_path, *, expires_at, max_clicks) Update link settings
client.links.delete(short_path) Delete a link
# Create with options
link = client.links.create(
    "https://example.com",
    custom_slug="my-link",
    expires_at="2025-12-31T23:59:59Z",
    max_clicks=1000,
)

# Paginate
page1 = client.links.list(limit=20, offset=0)
page2 = client.links.list(limit=20, offset=20)

# Update
updated = client.links.update("my-link", max_clicks=500)

# Delete
client.links.delete("my-link")

Analytics

Method Description
client.analytics.get_stats(short_path) Get raw per-click stats for a link
client.analytics.get_aggregate_stats(short_path, *, period=None) Get rolled-up (aggregated) stats for a link
stats = client.analytics.get_stats("abc123")
print(stats.total_clicks)
for click in stats.clicks:
    print(click.country, click.device, click.timestamp)

get_aggregate_stats returns server-side aggregations (clicks-by-day, country/device/UTM breakdowns, unique visitors). The breakdowns present are tier-gated — free-tier responses include an upgrade_for_more hint and omit the richer breakdowns; higher tiers populate device_breakdown, utm_breakdown, hour_breakdown, etc.

agg = client.analytics.get_aggregate_stats("abc123", period="30d")
print(agg.total_clicks, agg.unique_visitors, agg.tier)
for day in agg.clicks_by_day:
    print(day.date, day.clicks)
print(agg.country_breakdown)  # {"US": 100, ...}

if agg.device_breakdown:                       # populated on Pro+
    print(agg.device_breakdown.mobile, agg.device_breakdown.desktop)
if agg.upgrade_for_more:                        # present on free tier
    print(agg.upgrade_for_more.message, agg.upgrade_for_more.available)

QR Codes

Method Description
client.qr.get_url(short_code, *, size=300, color='000000', bg_color='FFFFFF') Build QR image URL

No HTTP request is made — this method constructs and returns the URL string.

url = client.qr.get_url("abc123", size=400, color="FF5733", bg_color="FFFFFF")
# https://awsys.co/api/qr/abc123?size=400&color=FF5733&bgColor=FFFFFF

Folders

Method Description
client.folders.list() List all folders
client.folders.create(name, *, color) Create a folder
client.folders.delete(folder_id) Delete a folder
client.folders.assign_link(short_path, folder_id) Assign a link to a folder
client.folders.remove_link(short_path) Remove a link from its folder
folder = client.folders.create("Q1 Campaign", color="#FF5733")
client.folders.assign_link("abc123", folder.id)

folders = client.folders.list()
for f in folders.folders:
    print(f.name, f.link_count)

client.folders.remove_link("abc123")
client.folders.delete(folder.id)

Bulk Create

Method Description
client.bulk.create(urls) Create multiple links in one request
result = client.bulk.create([
    {"url": "https://example.com/page-1"},
    {"url": "https://example.com/page-2", "custom_slug": "page-two"},
    {"url": "https://example.com/page-3", "max_clicks": 100},
])
print(f"Created: {result.created}, Failed: {result.failed}")
for r in result.results:
    print(r.short_url, r.success)

Me

Method Description
client.me.get() Get the authenticated user's profile
me = client.me.get()
print(me.email, me.subscription_tier, me.is_premium)

Usage

Method Description
client.usage.get() Get live account consumption + tier limits

Distinct from me.get() (static profile), usage.get() returns live counters (links, clicks, QR codes, API calls, tracked clicks), the current tier limits, and any active overage state. Limit fields may be an integer or the literal string "unlimited".

usage = client.usage.get()
print(usage.total_links, usage.tracked_clicks_this_month)
print(usage.limits.monthly_links)  # int or "unlimited"
if usage.overage.active:
    print(f"Overage charge: {usage.overage.estimated_charge_cents}c")

Web2App

Method Description
client.web2app.consume_session(token) Consume a Web2App deep-link session

Web2App sessions are single-use (consumed on read) with a 24-hour TTL. Unknown, expired, or already-consumed tokens raise AwsysNotFoundError; malformed tokens raise AwsysValidationError.

session = client.web2app.consume_session("0123456789abcdef0123456789abcdef")
print(session.link_id, session.utm_params, session.country)

Imports

Method Description
client.imports.start(*, provider, access_token, target_namespace=None, scan_only=None) Start a provider link-import job
client.imports.get_status(job_id) Get the current state of an import job
client.imports.cancel(job_id) Cancel an in-progress import job
client.imports.list(*, limit=None) List import jobs for the account
client.imports.wait_for_completion(job_id, *, poll_interval=2.0, timeout=120.0) Poll until the job reaches a terminal state

Import jobs run asynchronously server-side. start returns a pending ImportJob; poll get_status (or use wait_for_completion) until the status is one of completed, partial, failed, or cancelled. Pass scan_only=True to preview what would be imported without writing links.

job = client.imports.start(provider="bitly", access_token="<bitly-token>")
print(job.id, job.status)

# Block until the import finishes (raises TimeoutError if it overruns).
done = client.imports.wait_for_completion(job.id, poll_interval=5.0, timeout=300.0)
print(done.status, done.counts.written, done.counts.errored)
for err in done.errors:
    print(err)

# Or drive it manually
for j in client.imports.list(limit=10):
    print(j.id, j.provider, j.status)
client.imports.cancel(job.id)

All imports methods are available identically on AsyncClient (await client.imports.start(...), await client.imports.wait_for_completion(...)).

Error Handling

All errors inherit from AwsysError.

from awsysco import (
    Client,
    AwsysError,
    AwsysAuthError,
    AwsysForbiddenError,
    AwsysNotFoundError,
    AwsysConflictError,
    AwsysValidationError,
    AwsysRateLimitError,
)

try:
    link = client.links.get("nonexistent")
except AwsysNotFoundError:
    print("Link not found")
except AwsysAuthError:
    print("Invalid API key")
except AwsysConflictError as e:
    print(f"Slug already taken: {e.message}")
except AwsysValidationError as e:
    print(f"Bad request: {e.message} ({e.code})")
except AwsysRateLimitError as e:
    print(f"Rate limited. Retry after {e.retry_after}s")
except AwsysError as e:
    print(f"API error {e.status}: {e.message}")
Exception HTTP Status When raised
AwsysValidationError 400 Invalid request parameters
AwsysAuthError 401 Missing or invalid API key
AwsysForbiddenError 403 Insufficient permissions
AwsysNotFoundError 404 Resource does not exist
AwsysConflictError 409 Custom slug already taken
AwsysRateLimitError 429 Too many requests
AwsysError 5xx Server errors

All exceptions expose .message, .code, .status, and .raw attributes.

Rate Limiting

The SDK automatically retries on 429 Too Many Requests with exponential backoff (up to 3 retries). The Retry-After response header is respected.

from awsysco import AwsysRateLimitError

try:
    link = client.links.create("https://example.com")
except AwsysRateLimitError as e:
    print(f"Still rate limited after retries. Retry after: {e.retry_after}s")

Custom Base URL

# Point at staging
client = Client(
    api_key="awsys_...",
    base_url="https://staging.awsys.co",
)

Context Manager

with Client(api_key="awsys_...") as client:
    link = client.links.create("https://example.com")
    print(link.short_url)
# HTTP connections are closed automatically

Models

All responses are parsed into Pydantic v2 models:

Model Fields
Link id, short_url, short_code, long, clicks, created, expires_at, max_clicks, password_protected
LinkList links, total, has_more
LinkStats short_code, total_clicks, clicks
ClickEvent timestamp, country, device, browser, os, referrer
Folder id, name, color, link_count, created_at
FolderList folders, limit, used
BulkResult created, failed, results
BulkLinkResult success, short_url, long, error
MeResponse uid, email, subscription_tier, user_prefix, is_premium, features, limits
UsageStats total_links, total_clicks, links_created_this_month, qr_codes_this_month, folder_count, api_calls_this_month, tracked_clicks_this_month, tier, limits, has_api_key, api_key_created_at, user_prefix, is_premium, overage
UsageLimits links_per_month, monthly_links, daily_links, monthly_tracked_clicks, qr_codes, folders (each int or "unlimited"), api_calls_per_month, custom_slugs
UsageOverage active, started_at, expires_at, hours_until_drop, clicks_this_cycle, spending_limit_cents, estimated_charge_cents
Web2AppSession success, link_id, utm_params, routing_rule, country, clicked_at
ImportJob id, user_id, provider, status, scan_only, target_namespace, scope_filter, counts, errors, created_at, updated_at
ImportCounts fetched, transformed, written, errored
AggregateAnalytics short_code, full_path, period, total_clicks, unique_visitors, clicks_by_day, country_breakdown, tier_limit, tier, device_breakdown, referrer_breakdown, browser_breakdown, os_breakdown, source_breakdown, hour_breakdown, utm_breakdown, upgrade_for_more
DayClicks / HourClicks date/hour, clicks
DeviceBreakdown mobile, desktop, tablet
UTMBreakdown sources, mediums, campaigns
UpgradeForMore available, message

Development Setup

git clone https://github.com/AlphaWaveSystems/awsysco-python-sdk.git
cd awsysco-python-sdk

pip install -e ".[dev]"

# Configure test credentials
cp .env.example .env.test
# Edit .env.test — add your AWSYS_API_KEY (staging recommended)

# Run tests
pytest

# Run with coverage
pytest --cov=awsysco --cov-report=term-missing

Contributing

  1. Fork the repository
  2. Create a feature branch (git checkout -b feat/my-feature)
  3. Make your changes and add tests
  4. Run pytest — all tests must pass
  5. Open a pull request

Please read SECURITY.md before contributing — never commit API keys.

License

MIT License — see LICENSE for details.

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

awsysco-1.3.0.tar.gz (44.9 kB view details)

Uploaded Source

Built Distribution

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

awsysco-1.3.0-py3-none-any.whl (46.7 kB view details)

Uploaded Python 3

File details

Details for the file awsysco-1.3.0.tar.gz.

File metadata

  • Download URL: awsysco-1.3.0.tar.gz
  • Upload date:
  • Size: 44.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for awsysco-1.3.0.tar.gz
Algorithm Hash digest
SHA256 f4151a044302acc2615556edebc8a56c190b6ddb9c0e9048a998a1dc6ed6807f
MD5 26c313663c26fac5f786f025c9bc135f
BLAKE2b-256 278700c11b40e85193a90e4e4e506546902d7c7f8fba5fcf2b0402617c0e442b

See more details on using hashes here.

Provenance

The following attestation bundles were made for awsysco-1.3.0.tar.gz:

Publisher: publish.yml on AlphaWaveSystems/awsysco-python-sdk

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

File details

Details for the file awsysco-1.3.0-py3-none-any.whl.

File metadata

  • Download URL: awsysco-1.3.0-py3-none-any.whl
  • Upload date:
  • Size: 46.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for awsysco-1.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 55bf05bc95eabf9bd9a041f61097c4ef4ab7db65841cfce2e5519bf997b5b53d
MD5 c035d4052d1ef7522944b89c7e3916b0
BLAKE2b-256 2b7b40709fc91bc28bdd68012e7a5182bcb0ac5052ef0aaa7edf21c29784ba51

See more details on using hashes here.

Provenance

The following attestation bundles were made for awsysco-1.3.0-py3-none-any.whl:

Publisher: publish.yml on AlphaWaveSystems/awsysco-python-sdk

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