The Norbelys OpenAPI spec: 83 operations, one document
A practical tour of api.norbelys.com/openapi.json: what's in it, how to import it into Postman or Insomnia, and why one spec drives every SDK and the CLI.
By Gabriel Lara, Developer Relations, Norbelys
Founder-reviewed ·How we research and correct articles
Every operation Norbelys’s REST API exposes is described, machine-readably,
at one URL: https://api.norbelys.com/openapi.json. It’s OpenAPI 3.1, it
currently describes 83 operations, and it isn’t a static file someone hand
maintains next to the code — it’s generated straight from the same typed
contract the API worker implements, so it can’t drift from what the API
actually does. This is a tour of what’s in it and what you can point at it.
What’s actually in the document
The spec follows the five canonical
patterns the whole API is built
from, so its shape is predictable before you’ve read a single operation:
resource CRUD (/v1/people, /v1/senders, /v1/domains, /v1/programs,
/v1/suppressions…), relationship sub-collections, ?view= projections,
:verb actions, and the /v1/jobs bulk pattern. Every request and response
schema carries a .describe() annotation with a realistic example, so the
generated spec is self-documenting — a field like givenName shows up with
an actual example value, not just a type, which is what makes Postman,
Insomnia, and code generators produce usable stubs instead of placeholder
strings.
curl https://api.norbelys.com/openapi.json | jq '.info, (.paths | keys | length)'
That single request tells you the API version and how many paths exist right now — useful as a sanity check before you generate anything against it, since the number changes as the surface grows.
Authentication is in the spec, not just the docs
An OpenAPI document isn’t only endpoint shapes — it also declares how a
client is supposed to authenticate, under securitySchemes, which is what
lets an imported Postman collection or a generated SDK pre-wire the right
header instead of you guessing. Norbelys supports two authentication paths,
both discoverable from the spec and from the developer portal:
a static, org-scoped API key (ak_…) sent as Authorization: Bearer ak_…
for headless and CI callers, and OAuth 2.1 for interactive agents that
register dynamically via the metadata at
/.well-known/oauth-protected-resource. Every operation in the document
references one of these schemes, so a client generator knows, per endpoint,
exactly what credential to attach — it isn’t left to a paragraph of prose
elsewhere that a code generator can’t read.
Why OpenAPI 3.1, and why that version matters
Norbelys publishes 3.1 specifically, not the more common 3.0. The practical
difference is that 3.1 aligns its schema format with modern JSON Schema
instead of the constrained subset 3.0 used, which matters most for how
precisely nullable fields, oneOf unions, and nested object validation get
described. Since Norbelys’s contract schemas carry real validation logic —
required fields, enum constraints, format rules on things like email and
Idempotency-Key — 3.1 is the version that can express that logic exactly,
rather than approximating it and leaving a generated client to under- or
over-validate.
Importing it into an API client
Postman, Insomnia, and most REST clients can import an OpenAPI URL directly and turn it into a full, organized request collection — no manual endpoint entry.
Open the import dialog
In Postman: File → Import. In Insomnia: Application → Preferences → Data → Import Data, or the Import button on a workspace.
Point it at the spec URL
Paste https://api.norbelys.com/openapi.json as a URL import rather than uploading a file — that way re-importing later always pulls the current spec.
Set the bearer token
Add an Authorization header (or the client's Bearer Token auth type) with your org-scoped API key: Authorization: Bearer ak_live_.... Keys are created in the app and scoped to one organization.
Confirm the base URL
The imported collection should resolve to https://api.norbelys.com/v1 for every request — check the collection or environment variable if your client separates server URL from paths.
From there every operation in the spec — every resource, action, view, and job — is a ready-to-run request with the right method, path, and example body already filled in.
Generating your own client
Because the spec is standard OpenAPI 3.1, it works with any generic OpenAPI
code generator, not just the four SDKs Norbelys ships. openapi-typescript,
openapi-generator-cli, or your language’s equivalent will all happily
consume it directly:
That’s a legitimate path if you want a client in a language Norbelys doesn’t
officially publish an SDK for, or you want generation baked into your own
build pipeline instead of depending on an npm/PyPI release cadence. The
official SDKs exist for convenience — @norbelys/sdk on npm, norbelys on
PyPI, plus Go and Ruby — but they’re generated from this exact document, so a
client you generate yourself against the same spec is functionally
equivalent, just without the hand-picked ergonomics (retry helpers, pagination
iterators) the official packages add on top.
Why “one spec” is the actual point
The interesting property of the OpenAPI document isn’t that it exists — plenty
of APIs publish a spec that’s really a static export nobody keeps current.
It’s that this one is the only description of the API surface anywhere,
and everything downstream is generated from it: the four SDKs, the norbelys
CLI, and the MCP server at mcp.norbelys.com/mcp, whose tool
list is built by walking the same operations this document describes (the
MCP side of that story — auth, tool count, the security model — is covered
in running cold email from your AI agent,
so it’s not repeated here).
That single-source property is also why the field names you see in the spec are the same ones you get back in every response, in every SDK, in every MCP tool call — there’s no serializer translating between “the API’s names” and “the SDK’s names,” because there’s only one name per field, chosen from Schema.org vocabulary where one exists. A rename in the contract shows up in the spec, the regenerated SDKs, and the CLI’s help output in the same release. Nothing gets hand-edited into sync after the fact, because there’s nothing to keep in sync — there’s one document, and everything else is a projection of it.
Reading it like a developer, not a machine
You don’t have to run a generator to get value from the spec. Fetching it and
grepping the paths object is often the fastest way to answer “does this
resource support that operation” without leaving your terminal — faster than
searching prose documentation, and guaranteed current because it’s the same
document the API worker validates requests against.
curl -s https://api.norbelys.com/openapi.json | jq '.paths | keys' | grep senders
Whichever client or language you end up calling it from, this document is the answer either way: it’s the one artifact that describes what you can actually call, regardless of which client or language you’re calling it from. Start here, then work outward to the five patterns that explain why every operation looks the way it does.
Frequently asked questions
Does the OpenAPI spec ever fall out of sync with the live API?
No. The spec is generated from the same contract package the API worker uses to implement every route, so there's no separate document to forget to update. If an endpoint changes, the same change that ships it regenerates the spec.
Do I need an API key to fetch the spec itself?
No. The OpenAPI document at api.norbelys.com/openapi.json is public and describes the surface, including its own auth requirements. You only need a key to actually call the described endpoints.
Which languages have an official generated SDK?
TypeScript (@norbelys/sdk on npm), Python (norbelys on PyPI), Go, and Ruby. All four are generated from the same OpenAPI document, so they stay behaviorally identical to each other and to the spec.
Can I generate a client in a language without an official SDK?
Yes. Because the document is standard OpenAPI 3.1, any generic OpenAPI code generator — openapi-typescript, openapi-generator-cli, and similar tools across other languages — can consume it directly and produce a working client.