saperly
Python SDK for the Saperly v2 API — the camelCase data plane (numbers, connections, messaging, voice, usage, consent, pricing, keys).
Autogenerated from the OpenAPI contract.
Install
pip install saperly # or: uv add saperly
Requires Python 3.9+. The async API needs no extra install (uses httpx).
Usage
from saperly import create_client
from saperly.api.numbers import numbers_list, numbers_provision
from saperly.api.voice import voice_place
client = create_client(api_key="sap_sk_live_...")
provisioned = numbers_provision.sync(client=client, body=...)
calls = numbers_list.sync(client=client)
Each operation module exposes four entry points:
sync(...)— returns the parsed body (orNone).sync_detailed(...)— returns aResponsewithstatus_code,headers,parsed.asyncio(...)/asyncio_detailed(...)— theasyncequivalents.
Convenience wrapper
Saperly holds a configured client so you don't thread base_url / token
through every call:
from saperly import Saperly
from saperly.api.usage import usage_summary
with Saperly(api_key="sap_sk_live_...") as saperly:
summary = usage_summary.sync(client=saperly.client)
Retries
create_client() / Saperly(...) retry idempotent requests
(GET/HEAD/OPTIONS/DELETE) once on 5xx + connection errors. POST/PATCH are
never retried. Tune or disable with retries:
client = create_client(api_key="sap_sk_live_...", retries=3) # or retries=0
Webhooks
Verify the signature on inbound Saperly webhooks. Saperly signs each delivery
x-saperly-signature: v1=<hex> (HMAC-SHA256 over f"{timestamp}.{body}") with
x-saperly-timestamp; verify_webhook checks the signature constant-time, then
the timestamp window (default 5 min).
from saperly import verify_webhook
result = verify_webhook(raw_body, secret, request.headers)
if not result.valid:
return Response(status_code=400)
# Dedup result.delivery_id for >= the tolerance window to defeat replays.
Agent brain (manual mode)
In manual mode a connection's "brain" drives a live phone call: Saperly POSTs a
signed {"event": ...} to the connection's webhook per call event and reads back
exactly ONE directive. saperly.agent turns that into a typed handler surface —
you register per-event handlers, the framework owns signature verification (401 on
a bad signature), parsing (400 on a malformed event), dispatch, and the fail-safe
rule (past the signature gate it always returns 200 with a graceful directive,
since a 5xx degrades a live call). Stdlib only (no pydantic, no extra dependency).
from fastapi import FastAPI
from saperly.agent import AgentBrain, accept, Reject
brain = AgentBrain(secret=SAPERLY_MANUAL_SECRET) # the connection's manualSecret
@brain.on_inbound_call
def open_call(event):
return accept(greeting=f"Hi, you've reached {event.to}. How can I help?")
@brain.on_turn
def turn(event):
return f"You said: {event.user_text}" # a str → Speak(...)
app = FastAPI()
app.mount("/manual", brain.asgi()) # or: brain.handler(raw_body, headers) -> (status, body)
A handler returns a directive (Speak, Reject, Transfer, …), a str (shorthand
for Speak), or None (a safe default). brain.asgi() mounts on FastAPI/Starlette
with no extra dependency; brain.handler(raw_body, headers) is the sync
framework-agnostic entry (brain.handle_async(...) from async code).
Auth
Scoped Saperly API key (sap_sk_live_…), sent as
Authorization: Bearer <key> (handled by AuthenticatedClient). Mint keys in
the dashboard.
Development
This package is generated — do not edit saperly/client.py,
saperly/api/, or saperly/models/. The hand-written facade is
saperly/__init__.py + saperly/_saperly.py, preserved across
regeneration.
bash scripts/generate.sh # regenerate from ../openapi.v2.json
uv run --no-project --with httpx --with attrs --with python-dateutil --with pytest \
python -m pytest tests/ # smoke tests
The generator is
openapi-python-client
(run via uvx); config in openapi-python-client.yaml. The contract source of
truth is the SaperlyApi Effect HttpApi in packages/api.
Release files for saperly 3.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| saperly-3.0.0.tar.gz | 74.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| saperly-3.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 306.7 kB
Release files / saperly-3.0.0.tar.gz
| Download URL | saperly-3.0.0.tar.gz |
|---|---|
| Size | 74.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d639191dd585029206666c2ee83775e6cec4f0191661fbf40396a7191247fcb3
|
|
BLAKE2b-256 checksum How to use checksums |
8f2e7206791171e57bbc7a59ddbb8322d605a314eea5c7e1150c593238bb1e88
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.9
|
Release files / saperly-3.0.0-py3-none-any.whl
| Download URL | saperly-3.0.0-py3-none-any.whl |
|---|---|
| Size | 231.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
c3e910f7d8f93ff7afc3ce1165179bd4a15655bdb1202877f5002618ba4db35f
|
|
BLAKE2b-256 checksum How to use checksums |
6dc0f16e41477b617d2fcd5739eea2f38e0b0c95de8dbea6af430c4d60c45c37
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.9
|