> ## Documentation Index
> Fetch the complete documentation index at: https://docs.blobhub.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Authentication

> Store credentials, inspect the identity a command runs as, and switch between profiles

Five commands decide who the CLI is. `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 plus the API URL it belongs to — 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.

In CI you need none of these commands. `BLOBHUB_API_KEY` resolves on its own, with no credentials file present at
all and with no warning — see [Configuration](/cli/configuration) for the full precedence ladder.

## `blobhub login`

Authenticate interactively and store the result as a named profile.

### Synopsis

```bash theme={null}
blobhub login [--profile <name>] [--api-url <url>]
```

| Flag        | Default                     | Description                       |
| ----------- | --------------------------- | --------------------------------- |
| `--profile` | `default`                   | Name to store this profile under. |
| `--api-url` | `https://api.blobhub.io/v1` | Base API URL to log in against.   |

### What it does

1. Prompts for the API URL, offering `--api-url`'s value; press Enter to take it.
2. Prompts for the API key without echoing it, so the key never appears on screen or in your shell history.
3. Fails with `AUTH_INVALID` on an empty key, **before** any network call.
4. Validates the key against `GET /v1/users/me`. A rejection there is also `AUTH_INVALID`.
5. Writes the profile into `~/.blobhub/credentials.yaml` at `0600` and prints the identity it resolved:

```text theme={null}
Logged in as Ada Lovelace (<user id>), profile 'default'
```

Under `--json` the payload carries `profile`, `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.

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`](#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.

### Errors

| Code                               | When                                                                                |
| ---------------------------------- | ----------------------------------------------------------------------------------- |
| `AUTH_INVALID`                     | The key was empty, or `GET /v1/users/me` rejected it.                               |
| `INSECURE_CREDENTIALS_PERMISSIONS` | An existing `credentials.yaml` is readable by group or other. `chmod 600` it.       |
| `CREDENTIALS_NOT_FOUND`            | An existing `credentials.yaml` is not valid YAML, or a stored profile is malformed. |

That last row is worth reading twice: `CREDENTIALS_NOT_FOUND` covers a file that is present but unusable, not only
one that is absent.

## `blobhub logout`

Remove a stored profile, or every profile with `--all`.

### Synopsis

```bash theme={null}
blobhub logout [--profile <name>] [--all]
```

| Flag        | Default             | Description                  |
| ----------- | ------------------- | ---------------------------- |
| `--profile` | the default profile | Profile to remove.           |
| `--all`     | off                 | Remove every stored profile. |

### 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:

```text theme={null}
Removed profile 'staging'
```

Under `--json` the payload carries `removed`, the list of profile names that went — empty when there was nothing
to remove.

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

| Code                               | When                                                                            |
| ---------------------------------- | ------------------------------------------------------------------------------- |
| `PROFILE_NOT_FOUND`                | No profile by that name — or no default profile to remove, when you named none. |
| `INSECURE_CREDENTIALS_PERMISSIONS` | `credentials.yaml` is readable by group or other.                               |
| `CREDENTIALS_NOT_FOUND`            | `credentials.yaml` is present but unparseable or malformed.                     |

`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

```bash theme={null}
blobhub whoami [--profile <name>] [--api-key <key>] [--api-url <url>]
```

| Flag        | Default                                                                  | Description                                  |
| ----------- | ------------------------------------------------------------------------ | -------------------------------------------- |
| `--profile` | the default profile                                                      | Profile to resolve.                          |
| `--api-key` | from the environment, then the profile                                   | API key, overriding profile and environment. |
| `--api-url` | from the environment, then the profile, then `https://api.blobhub.io/v1` | API URL, overriding profile and environment. |

### What it does

Resolves a key and a URL exactly as every other command does, calls `GET /v1/users/me` with them, then probes
`GET /v1/users/me/orgs` to characterise the key's scope, and prints four lines:

```text theme={null}
Profile: default
URL: https://api.blobhub.io/v1
User: Ada Lovelace (<user id>)
Scope: full
```

Under `--json` the payload carries `profile` (`null` when no profile was involved), `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

| Scope        | What it means                                                                   |
| ------------ | ------------------------------------------------------------------------------- |
| `full`       | The probe succeeded: the key can list the user's orgs.                          |
| `restricted` | The probe returned `403`. The key is org- or blob-scoped.                       |
| `unknown`    | The probe failed for some other reason. Raises the `SCOPE_UNVERIFIED` advisory. |

**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`.

### Errors

| Code                               | When                                                                  |
| ---------------------------------- | --------------------------------------------------------------------- |
| `AUTH_INVALID`                     | `GET /v1/users/me` rejected the key.                                  |
| `CREDENTIALS_NOT_FOUND`            | No key from flags, environment, or credentials file.                  |
| `PROFILE_NOT_FOUND`                | `--profile` or `BLOBHUB_PROFILE` names a profile that is not stored.  |
| `INSECURE_CREDENTIALS_PERMISSIONS` | `credentials.yaml` is readable by group or other.                     |
| `SCOPE_UNVERIFIED`                 | Advisory, on stderr: the scope probe failed. The exit code stays `0`. |

## `blobhub profile ls`

List stored profiles, marking the default.

### Synopsis

```bash theme={null}
blobhub profile ls
```

Takes no flags.

### What it does

Reads the credentials file — no network call — and prints one line per profile in name order, with `*` against
the default:

```text theme={null}
* default (https://api.blobhub.io/v1)
  staging (https://api.staging.example.com/v1)
```

Under `--json` the payload carries `profiles` — one entry per profile holding `name`, `url` 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

| Code                               | When                                                        |
| ---------------------------------- | ----------------------------------------------------------- |
| `INSECURE_CREDENTIALS_PERMISSIONS` | `credentials.yaml` is readable by group or other.           |
| `CREDENTIALS_NOT_FOUND`            | `credentials.yaml` is present but unparseable or malformed. |

## `blobhub profile use`

Set the default profile.

### Synopsis

```bash theme={null}
blobhub profile use <name>
```

| Argument | Default  | Description                       |
| -------- | -------- | --------------------------------- |
| `name`   | required | Profile name to make the default. |

### What it does

Points the credentials file's `default` at an already-stored profile, and prints:

```text theme={null}
Default profile set to 'staging'
```

Under `--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.

### Errors

| Code                               | When                                                                     |
| ---------------------------------- | ------------------------------------------------------------------------ |
| `PROFILE_NOT_FOUND`                | No profile by that name. Run `blobhub profile ls` to see what is stored. |
| `INSECURE_CREDENTIALS_PERMISSIONS` | `credentials.yaml` is readable by group or other.                        |
| `CREDENTIALS_NOT_FOUND`            | `credentials.yaml` is present but unparseable or malformed.              |

## See also

* [Concepts](/cli/concepts) — what is shared across the binary and what belongs to a blob domain.
* [Configuration](/cli/configuration) — the credentials file these commands write, and the precedence ladder above
  it.
* [Create API Key](/rest-api/shared/create-api-key) — where an org- or blob-scoped key comes from.
* [Utilities](/cli/utilities) — `doctor` diagnoses a credential these commands cannot explain.
* [Error codes](/cli/error-codes) — `AUTH_INVALID`, `PROFILE_NOT_FOUND` and every other code above, with its
  remediation.
