warmbly-py
The official Python SDK for the Warmbly API: REST resources, OAuth2, and a realtime gateway, with first-class sync and async support.
Installation
pip install warmbly
Interactive OAuth2 flows and secure token storage are an optional extra:
pip install "warmbly[oauth]"
Requires Python 3.10+.
Quickstart
import os
from warmbly import Warmbly
client = Warmbly(api_key=os.environ["WARMBLY_API_KEY"])
campaign = client.campaigns.create(name="Q3 outreach")
print(campaign.id)
for key in client.api_keys.list():
print(key.name, key.status)
The client reads WARMBLY_API_KEY from the environment automatically, so you
can also just write client = Warmbly().
Async
Every method has an await-able twin on AsyncWarmbly: swap the class, add
await, and iterate with async for:
import asyncio
import os
from warmbly import AsyncWarmbly
async def main() -> None:
client = AsyncWarmbly(api_key=os.environ["WARMBLY_API_KEY"])
campaign = await client.campaigns.create(name="Q3 outreach")
print(campaign.id)
async for key in client.api_keys.list():
print(key.name)
await client.close()
asyncio.run(main())
Authentication
The SDK supports all three Warmbly auth modes; each is sent as a bearer token.
| Mode | How |
|---|---|
| API key | Warmbly(api_key="wmbly_...") or WARMBLY_API_KEY env var |
| OAuth2 access token | Warmbly(api_key="wmat_...") (any bearer token works) |
| OAuth2 flow | from warmbly.oauth import OAuth2Client: see the OAuth guide |
OAuth2 in three lines
from warmbly.oauth import OAuth2Client
oauth = OAuth2Client(client_id="wmcid_...", client_secret="wmcs_...",
redirect_uri="https://app.example.com/callback")
# 1. Send the user to authorize (PKCE handled for you):
url, state, verifier = oauth.authorization_url(scopes=["read_campaigns", "send_campaigns"])
# 2. Exchange the code returned to your redirect URI:
token = oauth.exchange_code(code, state=state, expected_state=state, code_verifier=verifier)
# 3. Use the access token:
client = Warmbly(api_key=token.access_token)
You can also register and manage OAuth2 applications programmatically via
client.oauth_applications.create(...).
Realtime gateway
Subscribe to live events over a single resilient WebSocket connection (heartbeats, automatic reconnect, and session resume are handled for you):
import asyncio
from warmbly import AsyncGatewayClient
async def main() -> None:
gateway = AsyncGatewayClient(token="wmbly_...") # needs the realtime_subscribe scope
@gateway.on_event("CAMPAIGN_STARTED")
async def handle(payload: dict) -> None:
print("campaign started:", payload["campaign_id"])
await gateway.connect()
await gateway.subscribe("org:00000000-0000-0000-0000-000000000000")
await gateway.run_forever()
asyncio.run(main())
Pagination
List endpoints return an iterator that transparently fetches every page:
for campaign in client.campaigns.list(): # walks all pages
print(campaign.name)
page = client.api_keys.list() # or work a page at a time
print(page.data, page.has_more, page.next_cursor)
Error handling
Every error inherits from warmbly.WarmblyError. HTTP failures map to a
status-specific subclass carrying .status_code, .request_id, and the parsed
.body.
| Status | Exception |
|---|---|
| 400 | BadRequestError |
| 401 | AuthenticationError |
| 403 | PermissionDeniedError |
| 404 | NotFoundError |
| 409 | ConflictError |
| 422 | UnprocessableEntityError |
| 429 | RateLimitError (.retry_after) |
| 5xx | InternalServerError |
| network / timeout | APIConnectionError / APITimeoutError |
| OAuth token endpoint | OAuthError (.error, .error_description) |
from warmbly import Warmbly, RateLimitError, NotFoundError
client = Warmbly()
try:
client.campaigns.retrieve("missing")
except NotFoundError:
...
except RateLimitError as err:
print(f"retry after {err.retry_after}s (request {err.request_id})")
Configuration
client = Warmbly(
api_key="wmbly_...",
base_url="https://api.warmbly.com/v1", # or WARMBLY_BASE_URL
timeout=30.0, # seconds, or an httpx.Timeout
max_retries=2, # 408/409/429/5xx with backoff + jitter
)
Idempotency keys are added automatically to write requests so retries are safe;
pass idempotency_key=... to a method to supply your own.
Webhooks
Verify inbound webhook signatures before trusting a payload. The
X-Warmbly-Signature header is t=<unix>,v1=<hex>, where the digest covers
"{t}.{raw_body}"; the helper checks it in constant time and rejects a stale
timestamp as a replay.
from warmbly import verify_webhook_signature
event = verify_webhook_signature(
payload=request.body, # raw bytes, never re-serialized
signature=request.headers["X-Warmbly-Signature"],
secret=endpoint_secret,
)
Documentation
Full documentation, guides, and the API reference live at warmbly-py.readthedocs.io.
Contributing
Contributions are welcome! Please read CONTRIBUTING.md and our Code of Conduct to get started.
License
MIT © Warmbly
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 warmbly-0.2.0.tar.gz.
File metadata
- Download URL: warmbly-0.2.0.tar.gz
- Upload date:
- Size: 105.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
44441cea1028d1880316836476b0e5177a7e21b1bbb6a1fd3e8670c5dc31864d
|
|
| MD5 |
07b3d2e422f0924f2a52e904c68a186e
|
|
| BLAKE2b-256 |
e2add66fe7fbe7be9e4ad00967c52ba32a40444690b421d0692f19d7bf1bfe24
|
Provenance
The following attestation bundles were made for warmbly-0.2.0.tar.gz:
Publisher:
release.yml on warmbly/warmbly-py
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
warmbly-0.2.0.tar.gz -
Subject digest:
44441cea1028d1880316836476b0e5177a7e21b1bbb6a1fd3e8670c5dc31864d - Sigstore transparency entry: 2340847013
- Sigstore integration time:
-
Permalink:
warmbly/warmbly-py@c89a380765493d0af7aeaabe4430fe9515dfa147 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/warmbly
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@c89a380765493d0af7aeaabe4430fe9515dfa147 -
Trigger Event:
push
-
Statement type:
File details
Details for the file warmbly-0.2.0-py3-none-any.whl.
File metadata
- Download URL: warmbly-0.2.0-py3-none-any.whl
- Upload date:
- Size: 131.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a5071f6cc6c17be79b1653702057049ae452f40916f49adb7594eb7910c9d3d0
|
|
| MD5 |
3ff10dc2f0e6650a2b6f4c1e24d69700
|
|
| BLAKE2b-256 |
3cb54e7dbe06e160b4c02544c0452d5ec945f52a306606e9a29c789058661814
|
Provenance
The following attestation bundles were made for warmbly-0.2.0-py3-none-any.whl:
Publisher:
release.yml on warmbly/warmbly-py
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
warmbly-0.2.0-py3-none-any.whl -
Subject digest:
a5071f6cc6c17be79b1653702057049ae452f40916f49adb7594eb7910c9d3d0 - Sigstore transparency entry: 2340847036
- Sigstore integration time:
-
Permalink:
warmbly/warmbly-py@c89a380765493d0af7aeaabe4430fe9515dfa147 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/warmbly
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@c89a380765493d0af7aeaabe4430fe9515dfa147 -
Trigger Event:
push
-
Statement type: