Skip to content
← BlogDeveloper

Installing the Norbelys CLI and running your first command

Two ways to install the norbelys CLI, how it turns the REST API into commands automatically, and a full walkthrough from install to your first real call.

By Gabriel Lara, Developer Relations, Norbelys

Founder-reviewed ·How we research and correct articles

If you’d rather run a command than click through a dashboard, norbelys is the official command-line client for the Norbelys API — the same cold email and deliverability platform, just driven from a terminal instead of a browser. It isn’t a hand-maintained wrapper that drifts out of sync with the product — it’s a thin, generated client. Every resource command is built at startup straight from the same API contract that powers the OpenAPI spec and the SDKs, so a command either does exactly what the API does, or it doesn’t exist yet.

This is a start-to-finish install-and-run guide: get the binary on your machine, authenticate, and make your first real call.

Two ways to install

Pick whichever fits how you already manage tools. Both put the same norbelys binary on your PATH.

The install script downloads a small self-contained bundle from Norbelys’ own distribution point and doesn’t require Node or npm to be installed first:

Terminal
$ curl -fsSL https://cli.norbelys.com/install | shInstalling norbelys CLI...norbelys installed to ~/.local/bin/norbelys$ norbelys versionnorbelys 1.4.2

The npm package is the better fit if you’re already inside a Node project or CI image that has npm available, and want the CLI managed the same way as your other dependencies:

Terminal
$ npm i -g norbelysadded 1 package in 2s$ norbelys versionnorbelys 1.4.2

Either path gives you the same tool. There’s no “npm edition” with fewer commands — it’s the identical bundle, just distributed through a package manager instead of a shell script.

Authenticate before your first command

Every command needs a bearer credential, the same Authorization: Bearer token the raw API expects. The fastest way to get one is an org-scoped API key created from the dashboard:

Terminal
$ norbelys login --api-key ak_...Logged in to Acme Inc (org_8h2k...)

That’s the whole setup for now. There’s also a browser-based OAuth flow built into the CLI for interactive human logins — a separate post covers the tradeoffs between the two and where the credential actually ends up on disk in more depth than makes sense here.

Your first real command

Once you’re logged in, every resource in the API is available as a command. The shape mirrors the REST endpoint closely enough that if you know the API, you already know roughly what to type — GET /v1/people becomes something like norbelys people list, POST /v1/senders becomes norbelys senders create, and so on for every resource the contract defines.

Terminal
$ norbelys people list --limit 5ID EMAIL GIVEN NAMEcon_9f2a... jane@acme.com Janecon_7d1c... marco@northwind.io Marcocon_3b8e... li.wei@example.com Li

By default the output is a readable table, which is what you want when a human is reading the terminal. When a script is reading the output instead, switch to structured output so you can pipe the result into jq or feed it to another program without parsing a table:

Terminal
$ norbelys people list --limit 5 --json[{"id":"con_9f2a...","email":"jane@acme.com","givenName":"Jane"}, ...]

That table/object/JSON split isn’t an afterthought bolted onto one or two commands — it’s a property of the whole CLI, because output rendering is handled by one shared layer that every generated command runs through, not reimplemented per command.

Why every command is generated, not hand-written

This is worth understanding because it changes what you should expect from the tool. The CLI reads the same contract package that defines the OpenAPI spec and builds a command tree from it at startup — each resource’s Zod schema turns into CLI flags, each operation turns into a subcommand, and a single request builder turns whatever you typed into the right HTTP call: path parameters go into the URL, GET/DELETE calls become query parameters, everything else becomes a JSON body.

The practical upshot: the CLI cannot expose a command that doesn’t correspond to something the real API does, and it cannot silently fall behind when the API changes shape. If a field becomes required in the contract, the flag becomes required in the CLI on the next build. There’s no separate CLI team maintaining a parallel definition of what Norbelys does — there’s one definition, and the CLI is one of several things generated from it, alongside the SDKs and the MCP server’s tool catalog.

There’s also an escape hatch for anything the generated tree doesn’t cover yet — a raw api command that lets you hit any path directly with your authenticated session, useful the day after a new endpoint ships and before you’ve upgraded. It’s the same underlying request builder every generated command already uses — path parameters, query strings, and JSON bodies handled the same way — just with the resource and verb spelled out by hand instead of resolved from a subcommand name. In practice you’ll reach for it rarely: mostly the window between a new endpoint landing in the contract and your next norbelys upgrade picking it up.

Staying current

Because the command tree is generated from the contract, an out-of-date CLI binary is the one way it can drift — an old build simply doesn’t know about a resource added after it shipped. The CLI checks for this itself: it pings for a newer version roughly once a day, fails silently if that check can’t complete (a flaky network shouldn’t block you from running a command), and tells you plainly when an upgrade is available.

Terminal
$ norbelys upgradeUpgrading norbelys 1.4.2 -> 1.5.0...Done. Run `norbelys version` to confirm.

If you’re pinning a version deliberately — inside a Docker image, say, or a CI runner where you want reproducible builds — that’s fine; just know the update check exists and won’t interrupt a script if it fails, only if you explicitly run upgrade.

Composing the CLI with other tools

Because JSON output is a first-class mode rather than an afterthought, the CLI slots into whatever shell tooling you already use instead of asking you to learn a new one. jq for filtering and reshaping, grep for a quick match, wc -l for a row count, redirecting into a file for something you want to diff against tomorrow’s run — none of that requires anything special from the CLI beyond honoring --json consistently across every generated command, which it does because the flag is handled by the same shared output layer regardless of which resource you’re calling.

Terminal
$ norbelys people list --json | jq -r '.[].email' | sort > emails-today.txt$ diff emails-today.txt emails-yesterday.txt

That composability is really the point of a CLI existing at all next to a dashboard: a dashboard shows you one screen at a time; a terminal lets you chain small, well-understood tools together into something specific to the task in front of you, without waiting on a feature request for a view nobody’s built yet.

Where to go next

You’ve got the binary installed and one real call under your belt. From here: