login stores a credential, whoami reports the identity a command would
run as right now, logout removes one, and profile ls / profile use list and switch between the credentials
you have stored.
A credential is a profile — an API key, the API URL it belongs to, and optionally a realtime (WebSocket)
URL — kept in ~/.blobhub/credentials.yaml, written atomically at mode 0600 and refused on load if any group
or other permission bit is set. The CLI never repairs the mode for you, and it never prints a key: not in
command output, not in an error message, not in the --json payload.
That file is shared with
blobhub-worker. One login on a machine serves both binaries,
and both read the same default: and profiles: map. Three consequences worth knowing before you run the
commands below: logout --all removes the worker’s credential too; profile use moves which profile a bare
blobhub-worker start picks up; and any write to this file, by either binary — this CLI’s login,
logout and profile use alike, the worker’s own login, or a one-time pre-profiles migration on the
worker’s start — rewrites the whole file from what it read. From an older release of either binary, that
rewrite silently drops realtime_url from every stored profile, not only the one being touched — worth
knowing if the two binaries are not upgraded together on one machine. The worker also keeps a separate body of
local state per profile, which is why it — unlike this CLI — cares which name you log in under.BLOBHUB_API_KEY resolves on its own, with no credentials file present at
all and with no warning — see Configuration for the full precedence ladder. The other
option is to script login itself: --api-key-stdin (or --api-key-env) with --no-input stores a validated
profile with no terminal involved, which is worth the extra step when the automation needs a named, stored
identity rather than a key that lives only in one process’s environment.
blobhub login
Authenticate and store the result as a named profile — interactively, or scripted with no terminal at all.
Synopsis
--api-key, --api-url and --realtime-url each take exactly one of their three sources — the literal flag,
its -stdin sibling, or its -env <VAR> sibling — and the three are mutually exclusive: naming two for the
same value, or claiming stdin with more than one -stdin flag anywhere on the line, is a usage error, exit
2, checked before anything is read. --api-key-env and its two siblings always take the variable’s name;
none has a bare form that falls back to a fixed variable. Nothing in this workspace opens a WebSocket with the
realtime URL yet — it is only stored and reported, so it is in place when something does.
What it does
- Resolves the API key, API URL, and realtime URL, each independently from its own three sources. A source
that resolves to an empty value — an empty literal, an empty pipe, or a
-envvariable that is unset or blank — fails immediately, asINPUT_REQUIRED(orENV_VAR_NOT_SETfor the variable case). - Prompts for whatever the API key and API URL still lack — but only when
--no-inputis absent, no-stdinflag was used anywhere on the line, and stdin is a real terminal. Off that path, an unclaimed API URL silently takeshttps://api.blobhub.io/v1with no prompt and no error; an unclaimed API key is the one case with no such fallback (see step 4). The realtime URL is never prompted for, on any path — supply it with a flag or leave the profile without one. - When prompting is allowed, asks for the API URL on stderr first, offering that same default; press Enter to take it. Then asks for the API key without echoing it, so it never appears on screen or in your shell history.
- Fails with
INPUT_REQUIREDif the key is still unresolved here — no source claimed it and prompting wasn’t available — or if you were prompted for it and entered nothing. Either way, this happens before any network call. - Validates the key against
GET /v1/users/me. A rejection there isAUTH_INVALID. - Writes the profile into
~/.blobhub/credentials.yamlat0600and prints the identity it resolved:
--json the payload carries profile, url, realtime_url, and user — the record GET /v1/users/me returned — plus the envelope’s schema_version. The key is not among them, here or anywhere
else. realtime_url here always reports a concrete value, falling back the same way the flag does; the
stored profile only gets a realtime_url entry when one was actually supplied — nothing defaults it in
credentials.yaml itself.
The first profile you store becomes the default. Later logins add or replace a profile but leave the default
where it is; move it with blobhub profile use.
Unlike blobhub-worker, login has no identity-change guard. Logging in again — under the same profile name
or a new one — simply replaces or adds a profile, whoever the new key belongs to. A CLI switches identities by
design; a worker bound to one identity for the life of a process does not.
Because the store is shared, that has a consequence outside this binary: re-logging into a name a worker is
pinned to is caught at its next start, as USER_IDENTITY_MISMATCH. Nothing is lost — that is the right
place for it to surface, since the worker’s stored cursors belong to the old identity.
Errors
That last row is worth reading twice:
CREDENTIALS_NOT_FOUND covers a file that is present but unusable, not only
one that is absent.
Using a service-account key
login doesn’t distinguish a human’s key from a service account’s — store one
the same way, under whatever --profile name fits: blobhub login --profile ci, pasting the account’s key
at the prompt. The identity line it prints resolves to the account’s own name and id, and every command you
later run under that profile — --profile ci, or BLOBHUB_PROFILE=ci — acts as the account, not as whoever
minted the key. See blobhub whoami below for what else is different about a key scoped
this way.
blobhub logout
Remove a stored profile, or every profile with --all.
Synopsis
What it does
Removes the named profile — or the default one, when you name none — and rewrites the credentials file. With--all, every profile goes and the default is cleared. One line is printed per profile removed:
--json the payload carries removed, the list of profile names that went — empty when there was nothing
to remove.
logout --all also removes any credential blobhub-worker was using, since there is one store. Its next
start then fails CREDENTIALS_NOT_FOUND — for a reason recorded in this binary’s history, not its own.
Removing the default profile leaves the installation with no default at all, and nothing chooses a new one:
a later login sets the default only when no other profile exists. Until you run blobhub profile use, every
command must name its profile with --profile or BLOBHUB_PROFILE, or supply a key some other way.
Errors
logout --all against no credentials file at all is not an error: nothing is removed, nothing is printed, and the
exit code is 0.
blobhub whoami
Show the resolved identity, profile, and key scope.
Synopsis
What it does
Resolves a key, a URL and a realtime URL exactly as every other command does, callsGET /v1/users/me with
them, then probes GET /v1/users/me/orgs to characterise the key’s scope, and prints five lines:
--json the payload carries profile (null when no profile was involved), url, realtime_url,
user and scope, plus the envelope’s schema_version.
This is the command to run when you are unsure which credential a command will pick up. Profile: (none) means
no profile was involved at all — the key came from --api-key or BLOBHUB_API_KEY.
The scope line
A
restricted key is a valid key. By the time the probe runs, GET /v1/users/me has already accepted it —
the probe is enrichment, not authentication. restricted is the expected reading for exactly the org- and
blob-scoped keys the platform recommends for automation, and such a key drives every command here except the
admin-scoped reads.
unknown says nothing about the key either. It means the probe itself failed — a network error, or a server
error on that one route — so the CLI reports what it observed rather than guessing full.
A service account’s key is a user-scoped key — the kind Create API
Key mints when you target it directly — and it reports full here too, for
the same reason a personal key does: the probe only needs the key to authenticate as a user, not to carry any
particular role. Don’t read full as “more powerful” than restricted; it means “scoped to a user,” nothing
about capability.
What that scope still can’t do is change who can access what. Ten operations — minting and revoking API
keys, storing and deleting credentials, granting and revoking memberships, creating and retiring accounts,
minting an acting token, and changing an org’s or blob’s visibility — require a human behind the request,
and a service account is never one. The same key that reports full here gets 403 from every one of them,
regardless of the role it was minted with. Everything else is unaffected: blobhub workflow deploy,
scheduler deploy, every push, pull and execute in this binary is ordinary product work and runs exactly as
it does under a personal key. See
The Access Perimeter for the full model and for
impersonation, the way across it.
Errors
blobhub profile ls
List stored profiles, marking the default.
Synopsis
What it does
Reads the credentials file — no network call — and prints one line per profile in name order, with* against
the default and a [realtime: …] suffix on any profile that has one stored:
--json the payload carries profiles — one entry per profile holding name, url, realtime_url
(null when that profile never stored one) and a boolean default — and default, the default profile’s name
or null. No key appears in either.
No profiles, or no credentials file at all, prints nothing and exits 0.
Errors
blobhub profile use
Set the default profile.
Synopsis
What it does
Points the credentials file’sdefault at an already-stored profile, and prints:
--json the payload carries default, the profile name now set as the default.
It stores nothing new: the profile must exist. This is how you recover a default after logout removed it, and
how you switch between environments without re-entering a key.
It also moves which profile a bare blobhub-worker start resolves — and with it which configuration, sessions
and cursors that worker picks up. The worker announces the profile it resolved on its first log line so the
change is visible rather than inferred, but if a worker runs on this machine, prefer naming its profile
explicitly in its service definition.
Errors
See also
- Worker authentication — the other half of the shared credential store.
- Concepts — what is shared across the binary and what belongs to a blob domain.
- Configuration — the credentials file these commands write, and the precedence ladder above it.
- Service Accounts — what a service account is and how to create one.
- Provisioning — worked recipes for CI, Docker, Kubernetes, systemd, and Ansible.
- Create API Key — where an org- or blob-scoped key comes from.
- Utilities —
doctordiagnoses a credential these commands cannot explain. - Error codes —
AUTH_INVALID,PROFILE_NOT_FOUNDand every other code above, with its remediation.

