# Profiles and credentials

After this guide, the CLI knows your environment, your credential, and the write protection, as a profile in a file or through environment variables only, and `szenera whoami` shows which account and organization it talks to.

> **Type:** guide
> **For:** Anyone pointing the CLI at an environment for the first time or working with several environments side by side
> **Prerequisite:** the built CLI, the base URL of your environment, and an API key `ak_…` (the standard way) or an account with a password
> **Where:** command line

A profile bundles two things: the credential and the mapping from service to base URL. Everything a command needs to know about the other end is stored there and nowhere else. Every profile belongs to one organization.

## Steps

1. Create the profile file `~/.config/szenera/profile.json` (`$XDG_CONFIG_HOME` is respected if it is set). Its structure:

   ```json
   {
     "default": "staging",
     "profiles": {
       "staging": {
         "services": { "*": "https://<basis-adresse-ihrer-umgebung>" },
         "credential": { "kind": "api-key", "key": "ak_…" },
         "readOnly": false
       },
       "prod": {
         "services": { "*": "https://<basis-adresse-der-produktion>" },
         "credential": { "kind": "api-key", "key": "ak_…" },
         "readOnly": true
       }
     }
   }
   ```

2. Enter the base URL under `services`. The `*` entry applies to everything without its own entry and is enough today. The address must begin with `http://` or `https://`, and a trailing slash is removed.

3. Choose the credential under `credential`. The type `api-key` with a value `ak_…` under `key` is the standard way. The other two types are described under “Options”.

4. Decide on `readOnly`. `true` blocks every command that changes something before a single network packet is created, so keep it for a production profile. **If the setting is missing, `true` applies.**

5. Set `default` to the profile that should apply without `--profile=`. If the file holds exactly one profile, that one applies even without a default. If it holds several without a default, the CLI aborts and names them.

6. Check the profile:

   ```bash
   szenera whoami
   szenera whoami --profile=prod --json
   ```

## Options

### Without a file: environment variables

Environment variables sit **above** the file: every variable that is set replaces the matching value of the chosen profile. `SZENERA_BASE_URL` and `SZENERA_API_KEY` alone carry a complete profile, with no file at all. It is then named `environment` and is not write-protected.

| Variable | Effect |
|---|---|
| `SZENERA_PROFILE` | Which profile of the file applies. `--profile=<name>` takes precedence |
| `SZENERA_BASE_URL` | Sets the `*` entry under `services` |
| `SZENERA_API_KEY` | API key. Replaces the profile’s credential |
| `SZENERA_OUTPUT` | `json` or `table`. `--json` and `--table` take precedence |
| `SZENERA_PROFILE_FILE` | Path of the profile file. Takes precedence over any search |

```bash
SZENERA_BASE_URL="https://<basis-adresse>" SZENERA_API_KEY="ak_…" szenera device list
```

### Where the CLI looks

The first hit wins:

| Location | When |
|---|---|
| `$SZENERA_PROFILE_FILE` | explicitly named |
| `~/.config/szenera/profile.json` | otherwise. `$XDG_CONFIG_HOME` is respected |

### Credential types

**`api-key`**: the standard way. The key travels as `Authorization: Bearer` with every call and does not expire after a quarter of an hour, so the CLI needs neither a cache nor a refresh for it. A value without a prefix that the API recognizes as a key is rejected before a network packet is created, as soon as a command needs the credential (exit 2). `--help` and `health` run even with a wrong value.

**`password`**: for the person at the computer. The profile carries `email` and `password` and, with two-factor sign-in, additionally `totpSecret` (Base32). The CLI computes the one-time code itself. The access token is then stored with permissions `0600` next to the configuration (`.szenera-token-<profil>.json`), is discarded one minute before it expires, and is renewed before every call if it will not last until the next round trip, even in the middle of a command that waits for minutes. If the account belongs to several organizations, the sign-in aborts instead of guessing.

```json
{ "credential": { "kind": "password", "email": "…", "password": "…", "totpSecret": "…" } }
```

**`none`**: public routes only. `szenera health` runs with it. Every other command aborts with a note that the profile carries no credential.

### Services per domain

Instead of `*`, an entry under `services` may name a platform domain (such as `fleet` or `composition`) and give it its own address. `*` remains for the rest. A name that is not a domain is rejected when the file is read, so a typo does not silently fall back to `*`.

## Check the result

- `szenera whoami` shows the ID, email, name, **Organization**, and the number of roles (route `GET /v1/me`).
- If a different organization appears than the one you expected, the profile points somewhere other than you intended. It is not the key that is wrong, but the choice.

## If something goes wrong

| Symptom | Meaning | Solution |
|---|---|---|
| `Keine Konfiguration gefunden — die Kommandozeile weiß nicht, mit welcher Umgebung sie sprechen soll.` (no configuration found) (exit 2) | Neither a file at one of the two locations nor `SZENERA_BASE_URL`. | Create the file or set the two variables. |
| `Unbekanntes Profil „…". … führt: dev, prod.` (unknown profile) (exit 2) | `--profile=` or `SZENERA_PROFILE` names a profile the file does not know. | Choose one of the names listed. |
| `… ist im Profil „prod" gesperrt — das Profil ist schreibgeschützt.` (blocked, the profile is write-protected) (exit 2) | A command that changes something meets a write-protected profile, or one without the setting. | Set `readOnly: false` if the profile may write. |
| `… „audience" muss „tenant" sein.` (audience must be tenant) (exit 2) | The file holds a profile for platform operations. `szenera` does not know it. | Remove the profile from the file or name a separate file through `SZENERA_PROFILE_FILE`. |
| `Kein API-Schlüssel: erwartet wird ein Wert mit dem Präfix …` (not an API key) (exit 2) | The value in the profile or in `SZENERA_API_KEY` is not a key. | Take the plaintext from the portal. See [Create an API key](/getting-started/create-api-key). |
| `… braucht einen Ausweis, das Profil „…" trägt keinen.` (needs a credential, the profile has none) (exit 1) | The profile has no `credential` or has `kind: "none"`. | Add `SZENERA_API_KEY=ak_…` or set `credential` in the profile. |
| HTTP 401 with exit 3 | The key is not valid: revoked, expired, or the profile points to a different environment than the key. | Check each profile with `szenera whoami --profile=<name>`. See [JSON output and exit codes](/cli/output-and-exit-codes). |
| `… gehört zu mehreren Organisationen …` (belongs to several organizations) | Password sign-in with an account that is in several organizations. | Use an API key of the organization you want. |

## Related

- [Get the CLI](/cli/get-cli)
- [JSON output and exit codes](/cli/output-and-exit-codes)
- [Create an API key](/getting-started/create-api-key)
