norbelys login: API key vs. browser OAuth, and where your credentials live
The CLI's two auth modes — org-scoped API keys and PKCE browser OAuth — when to use which, and why config.json's 0600 file permission actually matters.
By Gabriel Lara, Developer Relations, Norbelys
Founder-reviewed ·How we research and correct articles
Every norbelys command needs the same thing the raw API needs: a bearer
credential sent as Authorization: Bearer <token>. What differs is how
that token gets onto your machine in the first place, and the CLI supports
two distinct ways of answering that question. If you skipped straight here
without a CLI installed yet, start with the install-and-first-command
guide and come back.
The two modes
An API key, passed directly:
This is a static, org-scoped secret you generate from the dashboard and
paste in — or, for automation, hand to the CLI through an environment
variable instead of a flag ($NORBELYS_API_KEY, covered below). It works
today, for every kind of caller: a human on a laptop, a CI runner, a cron
job on a server nobody ever looks at interactively.
Browser OAuth, using the Authorization Code flow with PKCE and a
loopback redirect — the pattern RFC 8252
defines specifically for native apps and CLIs that can’t safely embed a
client secret in a binary anyone can download and disassemble. The idea:
the CLI spins up a tiny local HTTP server on 127.0.0.1, opens your default
browser to Clerk’s consent screen, you approve the request in the browser
you’re already logged into, and the redirect lands back on that local
server carrying an authorization code — no secret ever leaves your machine,
and PKCE stops anyone else on the box from intercepting the code and
trading it for a token themselves.
When to use which
The honest answer, right now, is that --api-key is the credential every
caller uses, because it’s the one that’s live. But it’s worth understanding
the intended split, because it explains why both exist and which one to
reach for once browser login is switched on.
A human at an interactive terminal is the case OAuth is built for. You already have a browser session logged into Norbelys; clicking “approve” there is less friction than generating a key in the dashboard, copying it, and pasting it into a terminal — and it sidesteps a long-lived static secret sitting in a config file for something you’re only doing to poke around for ten minutes.
A CI pipeline should always use an API key, full stop, both before and
after OAuth ships. A GitHub Actions runner has no browser to redirect to and
no human present to click “approve” — the loopback flow assumes an
interactive session, which a headless runner by definition doesn’t have.
The CI walkthrough covers
wiring $NORBELYS_API_KEY into a real pipeline in detail.
A script or scheduled job on a server falls in the same bucket as CI: no browser, no human, so it needs a static credential it can read from an environment variable on every run. This is also true for scripts you run locally but want to be deterministic and non-interactive — a nightly cron entry shouldn’t depend on a browser tab popping up while you’re asleep.
The resolution order backs this up: the CLI checks for a bearer token in
this priority — an environment variable first, then whatever’s saved in
your active profile, refreshing an OAuth token if it’s the kind that
expires. That ordering exists specifically so a script can override
whatever’s persisted on disk without touching it, by exporting
NORBELYS_API_KEY for just that one invocation.
Where the credential actually lives
norbelys login — either mode — writes the result to
~/.config/norbelys/config.json, one profile per org. It’s a plain JSON
file; nothing exotic, nothing encrypted at rest beyond your operating
system’s own disk protections.
That -rw------- is mode 0600, and the CLI sets it deliberately — read
and write for the file’s owner, nothing at all for anyone else on the
machine. It’s the same permission pattern you’d expect from ~/.ssh/
private keys, an AWS credentials file, or a GitHub CLI token: any of those
files being world- or group-readable turns “I have a shared machine” or “a
misconfigured backup job” into “anyone with local access can read every
credential I have.”
Why the permission actually matters
It’s easy to skim past a file permission as boilerplate, but think through
what’s sitting in that file: an API key that can create and delete data in
your org, or a token that can be exchanged for one. On a single-user laptop
the practical risk is low. It stops being low the moment you’re on a shared
build server, a container image someone else can shell into, a machine with
multiple local accounts, or a CI runner where an unrelated build step —
your own dependency’s postinstall script, say — happens to run as the same
user and go looking through $HOME.
The 0600 default also explains why $NORBELYS_API_KEY is the right choice
for automation rather than making the CI runner call norbelys login
itself: a build environment often persists less predictably than a laptop
home directory, and a secret manager injecting an environment variable for
the duration of one job is a narrower exposure window than a file sitting
on disk between runs.
Logging out
norbelys logout removes the active profile’s saved credential. If you
generated an API key specifically for a short-lived task — testing
something on a machine that isn’t yours, for instance — log out when you’re
done, and revoke the key from the dashboard rather than trusting yourself to
remember it’s still valid six months later.
What this looks like once OAuth is live
Once browser login is switched on, the day-to-day experience for a human on
their own laptop should feel closer to logging into any other SaaS tool
from a CLI — norbelys login, a browser tab opens, you approve, the tab
closes, and the terminal picks up a short-lived token it refreshes on your
behalf rather than a static key that’s valid until someone manually revokes
it. That’s a meaningfully better security posture for interactive use: a
token that expires and refreshes limits how long a leaked credential stays
useful, in a way a long-lived API key sitting in a config file doesn’t. It
doesn’t change anything about how CI or scripts should authenticate — that
answer stays --api-key and $NORBELYS_API_KEY either way, for the reasons
above.
One profile, one org — worth knowing before you scale up
Everything in this post assumes a single org. The moment you’re managing
more than one — an agency with several client workspaces is the common
case — the same config.json starts holding multiple profiles side by
side, and a new failure mode becomes possible: running a command against
the right credential but the wrong org. That’s common enough, and specific
enough, to deserve its own walkthrough rather than a paragraph here.
Next steps
- Install the CLI and run your first command if you haven’t yet.
- Automate ops checks in CI — the API-key path in a real GitHub Actions pipeline.
- Managing multiple orgs from one terminal — what a config file with several profiles actually looks like, and how to avoid running a command against the wrong one.
- Norbelys agent authentication covers the OAuth 2.1 path used by AI agents specifically, which is a related but separate flow from the CLI’s own login.
- The developer portal is the hub for everything else that’s built on the same API — the OpenAPI spec, the SDKs, and the MCP server.