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.
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 saperly-3.0.0.tar.gz.
File metadata
- Download URL: saperly-3.0.0.tar.gz
- Upload date:
- Size: 74.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d639191dd585029206666c2ee83775e6cec4f0191661fbf40396a7191247fcb3
|
|
| MD5 |
30587f76059aae487baedca1a29a7821
|
|
| BLAKE2b-256 |
8f2e7206791171e57bbc7a59ddbb8322d605a314eea5c7e1150c593238bb1e88
|
File details
Details for the file saperly-3.0.0-py3-none-any.whl.
File metadata
- Download URL: saperly-3.0.0-py3-none-any.whl
- Upload date:
- Size: 231.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c3e910f7d8f93ff7afc3ce1165179bd4a15655bdb1202877f5002618ba4db35f
|
|
| MD5 |
a9aa1d31253e7107d4702db1e9132090
|
|
| BLAKE2b-256 |
6dc0f16e41477b617d2fcd5739eea2f38e0b0c95de8dbea6af430c4d60c45c37
|