# CLI setup (/cli/setup)



<SurfaceStatus id="cli" />

Install `riffads`, point it at the API host, give it a key. Three minutes.

## You need [#you-need]

* Node 20.10.0 or newer.
* An API key (`sk_live_...`). Owners and admins create keys at [app.riffads.com/api-keys](https://app.riffads.com/api-keys). Tick **Generate** if the key should start jobs.
* A server, build box or your own machine. Never a browser: the key is a secret.

## Install [#install]

```bash title="Terminal"
npm install -g riffads

# or run it without installing
npx riffads@latest --version
```

## Set the host [#set-the-host]

```bash title="Terminal"
export RIFFADS_BASE_URL="https://app.riffads.com"
```

<Callout type="warn" title="Always set the host">
  The CLI defaults to `https://riffads.com`, which does not serve the API. Set `RIFFADS_BASE_URL`, pass `--base-url https://app.riffads.com`, or let `riffads login --base-url ...` save it. Wrong host: `... did not answer as a RiffAds API endpoint. Check --base-url.`
</Callout>

## Sign in [#sign-in]

```bash title="Terminal"
riffads login --base-url https://app.riffads.com
```

It asks for the key in a hidden prompt, checks it, then saves it. Or pipe the key in:

```bash title="Terminal"
pass show riffads/ci | riffads login --base-url https://app.riffads.com
```

`login` takes the key from, in order:

1. `--api-key <key>` (lands in shell history, avoid it)
2. `RIFFADS_API_KEY`
3. stdin, when it is not a terminal
4. A hidden prompt, only on a terminal (so CI never hangs)

A refused key exits 3 and writes nothing. A read-only key saves fine, then `generate` fails with `insufficient_scope`.

```json title="~/.riffads/config.json"
{
  "api_key": "sk_live_xxxxxxxxxxxx",
  "base_url": "https://app.riffads.com"
}
```

Directory mode 0700, file mode 0600.

## Check what it will use [#check-what-it-will-use]

```bash title="Terminal"
riffads whoami
```

```text title="stdout"
host    https://app.riffads.com (from file)
key     ****a91f (from file)
credits 4,200 credits
```

Every command picks the key and host from, in order:

| Order | Source      | Set with                              |
| ----- | ----------- | ------------------------------------- |
| 1     | Flag        | `--api-key <key>`, `--base-url <url>` |
| 2     | Environment | `RIFFADS_API_KEY`, `RIFFADS_BASE_URL` |
| 3     | Saved login | `~/.riffads/config.json`              |

<Callout type="warn" title="The environment beats your saved login">
  If `RIFFADS_API_KEY` is set, it wins over the key you just saved. Wrong workspace? `whoami` shows `(from env)`. Unset the variable.
</Callout>

Empty or whitespace values count as not set.

## Environment variables [#environment-variables]

| Variable              | Effect                                                              | Default               |
| --------------------- | ------------------------------------------------------------------- | --------------------- |
| `RIFFADS_API_KEY`     | The key. Beats the saved login.                                     | none                  |
| `RIFFADS_BASE_URL`    | The host. Set it to `https://app.riffads.com`.                      | `https://riffads.com` |
| `RIFFADS_CONFIG_HOME` | Folder for `config.json`.                                           | `~/.riffads`          |
| `NO_COLOR`            | Non-empty: no colour, no spinner.                                   | unset                 |
| `FORCE_COLOR`         | Anything but `0`: colour and spinner on. Beats `NO_COLOR` and `CI`. | unset                 |
| `CI`                  | Anything but empty, `0` or `false`: no spinner.                     | unset                 |

## CI [#ci]

Skip `login`. Set two variables from your secret store and check them first.

```bash title="CI"
export RIFFADS_API_KEY="$RIFFADS_KEY"
export RIFFADS_BASE_URL="https://app.riffads.com"

riffads whoami   # free, exits 3 if the key or workspace is wrong
```

Exit codes, JSON output and a full CI script: [commands](/cli/commands).

## Sign out and rotate [#sign-out-and-rotate]

```bash title="Terminal"
riffads logout
```

* Deletes the local file only. The key still works. Always exits 0.
* Revoke keys at [app.riffads.com/api-keys](https://app.riffads.com/api-keys). Work already running finishes.
* Rotate: create a new key, `riffads login`, `riffads whoami`, revoke the old key.

## When sign-in fails [#when-sign-in-fails]

Errors print on stderr as `error: <message> (<code>)`.

| Message                                                                             | Meaning                                      | Exit |
| ----------------------------------------------------------------------------------- | -------------------------------------------- | ---- |
| `No RiffAds API key.`                                                               | No flag, env var or saved login.             | 3    |
| `This RiffAds API key is not valid.`                                                | Malformed or unknown key.                    | 3    |
| `This RiffAds API key has been revoked.`                                            | Create a new key.                            | 3    |
| `This RiffAds API key was not given the Generate scope, which this endpoint needs.` | Read-only key. Create one with **Generate**. | 3    |
| `The workspace this RiffAds API key belongs to is no longer available.`             | Workspace is gone.                           | 3    |
| `This RiffAds API key is sending requests too quickly.`                             | Rate limited. Wait and retry.                | 5    |
| `... did not answer as a RiffAds API endpoint. Check --base-url.`                   | Wrong host.                                  | 2    |
| `"..." is not a valid base URL.`                                                    | Host does not parse.                         | 2    |
| `"..." is not an http or https URL.`                                                | Wrong scheme.                                | 2    |

A workspace without a plan that includes the API gets `required_plan`, exit 3. Every code: [errors](/reference/errors).
