The norbelys Python SDK quickstart: pip install to your first call
Install the official Python SDK from PyPI, authenticate with an API key, and make your first typed call against the Norbelys API — a complete quickstart.
By Gabriel Lara, Developer Relations, Norbelys
Founder-reviewed ·How we research and correct articles
The official Python SDK for the Norbelys API is norbelys on
PyPI. It’s generated from the same OpenAPI spec as the Node SDK, the CLI, and
the MCP server, so if you’ve read about
how those stay in sync, this is
that contract showing up as a pip install. This quickstart takes you from a
clean virtual environment to a working call.
Why a generated Python client beats raw requests
You could call the API with requests and a hand-rolled Authorization
header, and that works fine for a one-off script. Where it stops working is
maintenance: every field rename, every new required parameter, every
endpoint you didn’t know existed has to be tracked by hand. A generated SDK
means the types in your editor are the types the API actually accepts today
— because they’re built from the same spec the API itself publishes at
https://api.norbelys.com/openapi.json.
That matters more in Python than it sounds like it should, because
requests.json() gives you a plain dict with zero guarantees. A typo in a
key name — given_name instead of givenName, say — fails silently at
runtime with a KeyError three functions away from where you made the
mistake. A typed client turns that into an error your IDE or mypy catches
before you run anything.
From a clean environment to a first call
Create a virtual environment
Standard practice for any Python project — isolates the SDK and its dependencies from your system interpreter and other projects.
Install the package
pip install norbelys pulls the client and its typed models. No separate stub package needed — types ship in the wheel.
Set your API key as an environment variable
NORBELYS_API_KEY, read by the client at construction time — never hard-code a key in a script you might commit or share.
Construct the client and make a read call
List a resource you already have data for, such as people or senders, to confirm auth and connectivity before you write anything.
Move to a write call
Create a sender or send a message once the read path works. Typed request models catch a missing or misspelled field before the request goes out.
Install and run
A minimal script constructs a client and calls a resource method. Python
convention maps the API’s lowerCamelCase fields
(givenName, familyName) to idiomatic snake_case attributes
(given_name, family_name) on the returned model, the way most typed
Python API clients do:
import os
from norbelys import Norbelys
client = Norbelys(api_key=os.environ["NORBELYS_API_KEY"])
response = client.people.list(limit=5)
for person in response.data:
print(person.email, person.given_name)
A first write: creating a sender
sender = client.senders.create(
email="outreach@yourdomain.com",
name="Outreach",
)
print(sender.id) # snd_...
Pass a malformed payload — a required field missing, a value of the wrong type — and a typed client raises a validation error locally, before the request is sent, rather than letting the API reject it and leaving you to parse the error response by hand.
Handling errors idiomatically
Every Norbelys API error uses one shape,
{ error: { code, message, field? } }, regardless of which resource or
endpoint raised it. A Python SDK worth using turns that into a proper
exception hierarchy you can catch with normal try/except, rather than
making you inspect a response object after the fact:
from norbelys import APIError
try:
client.senders.create(email="not-an-email")
except APIError as err:
print(err.code, err.message, err.field)
That’s the pattern to reach for in any script that isn’t purely
throwaway — catch the specific exception, branch on err.code for the
error conditions you expect (a duplicate sender, a suppressed address), and
let anything unexpected propagate.
Pagination, the Pythonic way
The API pages by cursor — never offset — because IDs are prefixed cuid2 and
aren’t time-sortable, so ordering is always by createdAt. A well-built
Python client turns that into something you can loop over naturally instead
of manually tracking a cursor variable:
for person in client.people.list_all():
process(person)
Whether your installed version calls it list_all, exposes an iterator on
the paginated response object, or something else, the underlying mechanism
is the same opaque (createdAt, id) cursor described in
the OpenAPI spec walkthrough — the
Python idiom just hides the bookkeeping.
Type checking with mypy
Because the client’s models are generated from the OpenAPI spec, running
mypy over code that uses it catches the same class of mistakes a
TypeScript compiler would on the Node side — a field name that doesn’t
exist, a value passed as str where the model expects int, a required
argument left off a create() call. This is easy to skip in a quick script,
but worth turning on for anything that runs unattended (a scheduled job, a
production pipeline), because a caught type error is a five-second fix in
your editor versus a failed job at 3 a.m.:
pip install mypy
mypy main.py
Async support for concurrent calls
If you’re pulling a large number of records — verifying a batch of
addresses, or fetching detail on thousands of people — a synchronous
client making one request at a time will be your bottleneck, not the API.
Expect an async client variant alongside the synchronous one, following the
same pattern most modern Python HTTP clients use (httpx, for example)
so you can fan requests out with asyncio.gather instead of a serial loop:
import asyncio
from norbelys import AsyncNorbelys
async def main():
client = AsyncNorbelys(api_key=os.environ["NORBELYS_API_KEY"])
results = await asyncio.gather(
*(client.people.find(person_id) for person_id in ids)
)
return results
asyncio.run(main())
Reach for this once a script’s runtime is dominated by waiting on network calls rather than by CPU work — the usual signal that concurrency, not a faster machine, is the fix.
Configuring timeouts and retries
As with the Node SDK, expect constructor options beyond api_key for a
request timeout and a max_retries count, so a script that runs
unattended doesn’t hang indefinitely on a stalled connection or fail
outright on a single transient network error:
client = Norbelys(
api_key=os.environ["NORBELYS_API_KEY"],
timeout=10.0,
max_retries=2,
)
Pair retries with the Idempotency-Key header on any state-changing call —
a senders.create() retried automatically after a network blip should
never be able to create the sender twice.
Where Python fits
Python is the natural choice for data-science and ops scripts: a nightly job that pulls people or message events into a notebook, a one-off backfill, a Jupyter-driven exploration of campaign performance. It’s a less natural fit for a latency-sensitive edge Worker — if that’s your use case, the Node SDK or a direct fetch call is the better tool, and the SDK comparison guide lays out the trade-offs by use case in more depth.
Next steps
- If you’re wiring this into a service instead of a script, read the Node/TypeScript quickstart and compare.
- How the SDKs stay in sync with the API explains why a Python type mismatch essentially can’t happen silently.
- Need something closer to real-time than polling a script on a cron? See what to use instead of webhooks today.
- The full resource catalog lives at the developer portal, and Norbe, the AI operator is built on the same underlying API.