Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

supdesk

Python client for the SupDesk API.

Server-side SDK with two clients sharing one transport core: SupDesk (sync, httpx.Client) and AsyncSupDesk (async, httpx.AsyncClient). Requires Python 3.9+. The only runtime dependency is httpx.

Warning

Server-side only. Never ship your API key to a browser.

A SupDesk API key authenticates as your entire project. Anything that reaches a browser is public — bundlers inline it, DevTools shows it, and users can read it straight out of the network tab. Use this SDK from a backend you control and let your frontend talk to that.

pip install supdesk

Quick start

from supdesk import SupDesk

supdesk = SupDesk()  # api_key=... or $SUPDESK_API_KEY

# Auto-pages: iterating walks every page for you.
for submission in supdesk.submissions.list(status="open"):
    print(submission.title)

supdesk.submissions.create(
    type="bug",
    title="Export button does nothing",
    email="user@example.com",
    body="Clicking Export on the reports page has no effect.",
)

Async is the same shape, one await at a time:

import asyncio

from supdesk import AsyncSupDesk


async def main() -> None:
    supdesk = AsyncSupDesk()

    async for submission in await supdesk.submissions.list(status="open"):
        print(submission.title)

    async with supdesk:
        await supdesk.submissions.create(
            type="bug",
            title="Export button does nothing",
            email="user@example.com",
        )


asyncio.run(main())

API keys come from Workspace Settings → API Keys in the SupDesk console and are scoped to a single project. Reads work on every plan; writes (POST/PATCH/DELETE) require a paid plan and otherwise raise a ForbiddenError.

Read the key from a server-side environment variable — $SUPDESK_API_KEY, or any SECRET_* your platform provides

Security

The API key is a server-side secret. It is project-scoped, and on a paid plan it can create, edit and delete submissions, feedback, changelog entries, help center articles, message threads, waitlist signups and beta programs — and read every end-user email address in your project. It is not a publishable key, and SupDesk has no browser-safe equivalent.

# In a frontend you ship to users:
SupDesk(api_key="sd_live_…")

Never do this. The constructor has no browser guard because Python has no DOM to detect — but that is no invitation: if you ever find a key in a repository, a build log, or a client bundle, rotate it in Workspace Settings → API Keys.

Two habits worth keeping: give each environment its own key so one can be revoked without downtime elsewhere, and store the webhook signing secret server-side too, since it is what proves a delivery actually came from SupDesk.

Client options

from supdesk import SupDesk

supdesk = SupDesk(
    api_key="sd_live_…",  # or $SUPDESK_API_KEY
    base_url="https://api.supdesk.app/v1",  # default
    timeout=30.0,  # seconds; 0 or None disables
    max_retries=2,  # retries after the first attempt
    retry_unsafe_methods=False,
    default_headers={"x-app": "my-service"},
    http_client=None,  # inject an httpx.Client / httpx.AsyncClient
)

AsyncSupDesk takes the same arguments. Every method also accepts a final request_options={"timeout": ..., "headers": ...} for per-call overrides; authorization cannot be overridden per call. timeout is in seconds here (the Python idiom) rather than the JavaScript client's milliseconds.

Resources

Accessor Methods
submissions list get create
feedback list get create
changelog list get create update delete
messages list get create update delete add_message
waitlist list get create update delete
beta.programs list get create update delete
beta.testers list get create delete
articles list search get create update delete
article_categories list get create update delete

Pagination

list() returns a Page, which is both the current page and an iterable over everything after it.

page = supdesk.articles.list(status="published")

page.data  # just this page
page.pagination  # PaginationMeta(limit=20, offset=0, has_more=...)
page.has_next_page()
page.get_next_page()

for article in page:  # every page
    print(article.title)
page.to_list()  # everything, in memory

articles.search() is the exception — it returns a plain ranked list, not a page.

Errors

Every failure is a subclass of SupDeskError, so one except covers the lot while the class hierarchy still narrows to the specific case.

from supdesk import ForbiddenError, LimitReachedError, NotFoundError, SupDeskError

try:
    supdesk.articles.create(title="How to export")
except ForbiddenError:
    # Valid key, but writes need a paid plan.
    pass
except LimitReachedError:
    # Monthly submission quota exhausted.
    pass
except SupDeskError as error:
    print(error)
Class Status Code
InvalidRequestError 400 invalid_request
UnauthorizedError 401 unauthorized
ForbiddenError 403 forbidden
NotFoundError 404 not_found
RateLimitedError 429 rate_limited
LimitReachedError 429 limit_reached
InternalServerError 5xx internal_error

Plus SupDeskConnectionError, SupDeskTimeoutError, RequestTooLargeError (the API caps requests at 1 MB, checked before sending), SupDeskConfigurationError and SupDeskSignatureVerificationError. SupDeskAPIError carries status, code, headers, body and request_id.

Retries

The client retries with exponential backoff and jitter (500 ms base, 8 s cap), honouring Retry-After when a proxy supplies one. Two behaviours are worth knowing about:

  • limit_reached is never retried. It shares HTTP 429 with rate_limited, but a monthly quota will not clear inside a backoff window — retrying just burns more of your requests-per-minute budget. The two are told apart by code, not status.
  • POST is not replayed on network errors or 5xx by default. SupDesk has no idempotency key, and submissions.create / feedback.create are metered, so a request that failed after the server accepted it would double-charge your quota and file the end user's ticket twice. A 429 rate_limited is still retried on any method, because the server states it did not process the request. Opt in with retry_unsafe_methods=True.

Webhooks

Verification is synchronous: hmac + hashlib, with a constant-time hmac.compare_digest. Pass the raw body plus the X-SupDesk-Signature header.

from supdesk import construct_event_from_headers


def on_webhook(payload: bytes, headers: dict) -> None:
    event = construct_event_from_headers(payload, headers, secret)
    if event.event == "waitlist_signup.joined":
        print(event.data["email"])

Pass the raw body. The signature covers the exact bytes SupDesk sent. Frameworks that parse JSON for you (Flask's request.get_json(), FastAPI's await request.json(), Django's JsonRequestParser) break verification, because a re-serialized dict will not reproduce the original whitespace and key order. Capture the raw bytes first:

  • Flask: request.get_data()
  • FastAPI / Starlette: await request.body()
  • Django: request.body

Lower-level helpers: verify_webhook_signature(payload, signature, secret) returns a boolean (never raises on a malformed header), construct_event throws on mismatch, compute_webhook_signature builds fixtures, and Webhooks(secret) binds all of them to one secret. payload accepts str or bytes.

Examples

Framework-specific, runnable integrations (FastAPI, Django, Flask, plus a no-framework script) live in examples/ with their own requirements.txt. They are not part of the published package.

Contributing

python3 -m venv .venv && ./.venv/bin/pip install -e ".[dev]"

./.venv/bin/ruff check . && ./.venv/bin/ruff format --check .
./.venv/bin/mypy src
./.venv/bin/python -m pytest --cov=supdesk --cov-report=term-missing --cov-fail-under=80

./.venv/bin/python -m build && ./.venv/bin/twine check dist/*

The suite runs entirely against httpx.MockTransport — no network — and is parametrized so each test body covers both the sync and async clients.

License

MIT

About

Server-side Python client for the SupDesk API. Submissions, feedback, changelog, help center, messages, waitlist, beta programs and signed webhooks.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages