Python SDK
bird-sdk (import name bird) is the official Python SDK for the Bird API. This page covers the client itself — install, configuration, errors, retries, pagination, and webhooks. The per-method reference lives on the channel pages, starting with Email.
Install
Codevoorbeeld
pip install bird-sdkCodevoorbeeld
# or
uv add bird-sdk
poetry add bird-sdkRequires Python 3.10+. The SDK is fully typed (py.typed), with Pydantic v2 response models.
Create a client
There are two clients: Bird (sync) and AsyncBird (async). They mirror each other method-for-method — await each call, async for over lists. Configuration is plain keyword arguments:
Codevoorbeeld
msg = client.email.send(
from_={"email": "onboarding@messagebird.dev", "name": "Bird"},
to=["delivered@messagebird.dev"],
subject="Hello from Bird",
html="<p>My first Bird email.</p>",
)
print(msg.id, msg.status)from_ is the Python spelling of the wire field from (from is a reserved word); the alias is handled for you. Responses are Pydantic v2 models that tolerate unknown fields, so a new server field never breaks an existing client.
api_key and base_url fall back to the BIRD_API_KEY / BIRD_BASE_URL environment variables, so Bird() with no arguments works when they are set. Use the client as a context manager (with Bird() as client: / async with AsyncBird() as client:) to close the underlying connection pool — and construct one client and reuse it; both are safe to share across threads or tasks.
Configuration
| Option | Description |
|---|---|
| api_key | API key; falls back to BIRD_API_KEY. |
| region / base_url | Region (or explicit base URL); falls back to the key prefix / BIRD_BASE_URL. |
| timeout, max_retries | Request timeout and retry budget; overridable per call. |
| webhook_secret | Signing secret for client.webhooks.unwrap. |
| email_defaults | Client-wide send defaults; a per-send value always wins. |
| http_client | Inject your own httpx.Client / httpx.AsyncClient. |
Every method also takes a trailing options for per-call timeout / max_retries / idempotency_key / extra_headers, and client.with_options(...) derives a new client that reuses the parent's connection pool:
Codevoorbeeld
client.email.send(
from_={"email": "onboarding@messagebird.dev", "name": "Bird"},
to=["delivered@messagebird.dev"],
subject="Hello from Bird",
text="My first Bird email.",
options={"timeout": 10, "max_retries": 0},
)How it's built
The wire models are generated from Bird's OpenAPI spec; everything you touch is a hand-written, idiomatic layer on top — the curated resource surface (client.email, client.webhooks), explicit keyword arguments, and the request lifecycle (retries, timeouts, idempotency) in one core shared by every method. The cross-SDK model is in SDK concepts.
Errors
Failures raise typed exceptions rooted at BirdError. APIError covers anything that goes wrong issuing a request — including transport failures like timeouts — so a single except APIError is enough for "the call failed". APIStatusError is the server-returned subset, carrying status_code, request_id, code (the stable E##### code), and type (the coarse error category). Below it sit RateLimitError (a 429, with retry_after in seconds) and ValidationError (a 422, with per-field details):
Codevoorbeeld
from bird import APIStatusError, RateLimitError, ValidationError
try:
client.email.send(
from_={"email": "onboarding@messagebird.dev", "name": "Bird"},
to=["delivered@messagebird.dev"],
subject="Hello from Bird",
text="My first Bird email.",
)
except RateLimitError as err:
print("rate limited; retry after", err.retry_after)
except ValidationError as err:
print(err.status_code, err.details)
except APIStatusError as err:
print(err.status_code, err.code, err.request_id)Transport-only failures are APIConnectionError and APITimeoutError — both subclasses of APIError, so they never slip past a broad except APIError. A bad webhook signature raises WebhookVerificationError.
Safe retries
Transient failures — timeouts, 429s, 5xx — retry automatically with jittered backoff that honors Retry-After (tune the budget with max_retries; zero disables). A mutation generates one idempotency key per logical call and reuses it across every retry attempt, so a retried send never double-applies. Pass idempotency_key in the per-call options to pin your own.
Pagination
List methods return a lazy page (SyncPage / AsyncPage); iterating it auto-paginates across cursors, fetching pages on demand:
Codevoorbeeld
for message in client.email.list(status="delivered"):
print(message.id)Codevoorbeeld
from bird import AsyncBird
async with AsyncBird() as client:
async for message in client.email.list(status="delivered"):
print(message.id)Stop iterating and no further pages are fetched.
Webhooks
client.webhooks.unwrap verifies a Standard Webhooks signature over the raw request body and returns a typed, discriminated event. Configure the signing secret on the client (webhook_secret=), and pass the exact bytes you received — parsing and re-serializing first breaks the signature:
Codevoorbeeld
# Pass the RAW request body (bytes) and the request headers.
event = client.webhooks.unwrap(request.body, request.headers)
if event.root.type == "email.delivered":
print(event.root.data.email_id)Verification is pure crypto — no network call — so it works the same in any web framework.
Escape hatch
Endpoints not yet on the typed surface are reachable through client.get / post / put / patch / delete, with the same auth, retries, and idempotency handling:
Codevoorbeeld
from bird import EmailMessage
message = client.get("/v1/email/messages/em_01krd...", cast_to=EmailMessage)
client.post("/v1/some/new/endpoint", body={"key": "value"})Find the paths in the API reference.
Next steps
- Email methods — send, get, list, and every param.
- Python quickstart — first send in three steps.
- SDK concepts — the cross-SDK model: errors, idempotency, pagination, webhooks.
- API reference — the underlying HTTP contract.