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

# Command line (ryvo)

> Install the Ryvo CLI, log in with an API key, drive agents, calls and campaigns from the terminal, and connect the Ryvo MCP server to Claude Code, Cursor, VS Code or Windsurf.

`ryvo` is the official command line for the [Ryvo API](/api-reference). Every command is a thin, typed wrapper over a public endpoint, so it can do exactly what your API key allows and nothing more.

## Install

Node.js 20 or newer.

<CodeGroup>
  ```bash npm theme={null}
  npm install -g @ryvo-so/cli
  ryvo --help
  ```

  ```bash npx (no install) theme={null}
  npx @ryvo-so/cli --help
  ```
</CodeGroup>

## Log in

The CLI uses a normal API key. It **cannot create keys**: the API does not allow it on purpose, so a leaked key can never mint another one. Create a key in the portal under **Settings → API keys** ([app.ryvo.so/configuracion/api-keys](https://app.ryvo.so/configuracion/api-keys)) with the fewest scopes you need, then:

```bash theme={null}
ryvo login                    # paste the key (input is hidden)
ryvo whoami                   # account, plan and the key's scopes
```

`ryvo login` checks the key with `GET /v1/me` before saving it. The key is stored in `~/.config/ryvo/config.json` (`%APPDATA%\ryvo\config.json` on Windows) with `0600` permissions where the system supports them. Only the prefix (`ryvo_live_abc123...`) is ever printed.

In CI, containers or with a secrets manager, skip the file and set the key in the environment. **`RYVO_API_KEY` always wins over the saved key**:

```bash theme={null}
export RYVO_API_KEY=ryvo_live_...
echo "$RYVO_API_KEY" | ryvo login --with-token   # or store it from stdin
```

`ryvo logout` removes the stored key from this machine. It does not revoke it; revoke it in the portal.

## Commands

| Group | What it covers |
| - | - |
| `ryvo agents` | `list`, `get`, `create`, `update`, `publish`, `archive`, `versions`, `version` |
| `ryvo calls` | `list`, `get`, `transcript`, `audio`, `create` |
| `ryvo conversations` | `list`, `get`, `messages` |
| `ryvo campaigns` | `list`, `get`, `create`, `cancel` (batch calls) |
| `ryvo webhooks` | `list`, `get`, `create`, `update`, `delete`, `rotate-secret`, `deliveries`, `delivery`, `retry`, `events` |
| `ryvo knowledge` | `list`, `get`, `add-url`, `upload`, `update`, `delete`, `usage`, `folders ...` |
| `ryvo phone-numbers` | `list`, `get`, `import`, `update`, `delete` |
| `ryvo billing` | `plan`, `credits`, `transactions`, `invoices`, `usage`, `price` |
| `ryvo voices`, `ryvo models` | the voice library and the model catalog (`models` needs no key) |
| `ryvo integrations`, `ryvo team`, `ryvo logs` | read-only views |
| `ryvo api` | any endpoint, raw (see below) |
| `ryvo mcp install` | connect the MCP server to your AI client |

`ryvo <group> --help` lists the options of each command. A few examples:

```bash theme={null}
ryvo agents list --runtime engine
ryvo calls list --from 2026-09-01 --status failed --all
ryvo calls transcript 0f1e2d3c-4b5a-4968-8776-655443322110
ryvo knowledge upload ./prices-2026.pdf --title "Price list 2026"
ryvo billing price --tts elevenlabs:eleven_flash_v2_5 --llm anthropic:claude-haiku-4-5
```

### Commands that spend credits

`ryvo calls create` places a real phone call and `ryvo campaigns create` launches one call per recipient. Both:

* **ask for confirmation** before sending anything. Pass `--yes` in scripts; without a terminal and without `--yes` they refuse instead of guessing.
* **always send an `Idempotency-Key`** (a new uuid unless you pass `--idempotency-key`) and print it, so a retry after a timeout reuses it and never dials twice. See [Idempotency](/idempotency).

```bash theme={null}
ryvo calls create --agent 4d0f3c2a-6b7e-4a1c-9f2d-1e2b3c4d5e6f --to +5215555550100 --var lead_name=Juan
ryvo campaigns create --name "September follow-ups" --agent <agent_id> --recipients numbers.csv
```

`--recipients` takes a JSON array of recipients or a text/CSV file with one E.164 number per line. The real protection is the key's credit cap, set in the portal.

### Pagination

Every list takes `--limit` (1 to 100) and `--cursor`. `--all` follows `next_cursor` until the last page, 100 items at a time. Without `--all`, the CLI prints the next cursor when there is more.

### The escape hatch: `ryvo api`

Like `gh api`: any method and path, with your key, retries and exit codes, printing the raw JSON.

```bash theme={null}
ryvo api GET /v1/models
ryvo api GET /v1/calls -q status=failed -q limit=5
ryvo api PATCH /v1/agents/<id> --data '{"name":"Front desk"}'
ryvo api POST /v1/batch-calls --data @campaign.json -H Idempotency-Key:campaign-0929
```

It only accepts paths, never full URLs, so the key is only ever sent to the Ryvo API. In Git Bash, write `v1/models` without the leading slash (Git Bash rewrites paths that start with `/`).

## JSON output and exit codes

Human tables are the default and may change between versions. For scripts, add `--json`: it prints the API response exactly as documented in the [API reference](/api-reference). With `--json`, errors are printed to stderr as `{ "error", "message", "request_id", "exit_code" }`.

Every error prints the API's `request_id`: quote it when you contact us. Requests answered with `429` or `503` are retried up to 3 times, honoring `Retry-After` (or `X-RateLimit-Reset`), and never waiting more than a minute. See [Rate limits](/rate-limits).

The exit code comes from the API's `error` code, so a script can branch without parsing text:

| Code | Meaning | API `error` |
| - | - | - |
| `0` | Success | |
| `1` | Unclassified error, or you answered no to a confirmation | |
| `2` | Wrong usage: missing option, invalid JSON, `--yes` needed | |
| `3` | No key, or the key is invalid, expired or revoked | `unauthorized` |
| `4` | The key or the plan does not allow it | `forbidden`, `insufficient_scope`, `plan_limit` |
| `5` | Not found | `not_found`, `agent_not_found` |
| `6` | The request did not pass validation | `invalid_json`, `invalid_body`, `invalid_query`, `invalid_idempotency_key` |
| `7` | Conflict with the current state | `conflict`, `unsupported_runtime`, `idempotency_conflict`, `idempotency_in_progress`, `agent_not_published`, `no_phone_number`, `agent_disabled` |
| `8` | Payment needed | `payment_required`, `key_budget_exhausted` |
| `9` | Rate limit, still exceeded after retrying | `rate_limit_exceeded` |
| `10` | Server or provider error | `internal_error`, `upstream_error`, `rate_limit_unavailable`, ... (5xx) |
| `11` | The API could not be reached (network, DNS, TLS) | |

A code the CLI does not know yet falls back to its HTTP status (for example any `402` is `8`).

## Connect the MCP server

`ryvo mcp install` adds the Ryvo MCP server (`https://mcp.ryvo.so`) to your AI client, so the assistant can read your agents, calls and campaigns.

```bash theme={null}
ryvo mcp install --client claude     # Claude Code, ~/.claude.json
ryvo mcp install --client cursor     # ~/.cursor/mcp.json
ryvo mcp install --client vscode     # the mcp.json of your VS Code profile
ryvo mcp install --client windsurf   # Windsurf (Devin Desktop), mcp_config.json
```

Add `--scope project` to write the project file instead (`.mcp.json`, `.cursor/mcp.json` or `.vscode/mcp.json`), `--config <path>` to choose the file, and `--dry-run` to see the result without writing.

* It **merges**: other servers and settings in the file are kept, and a timestamped backup (`<file>.bak-20260929T123456Z`) is written first. A file with comments is left untouched and the entry is printed for you to paste.
* **The key is never written to the file.** Claude Code, Cursor and Windsurf read it from the `RYVO_API_KEY` environment variable of the client; VS Code asks for it once and stores it encrypted.

<Warning>
  Use a **read-only key** for the MCP (`agents:read`, `calls:read`, `conversations:read` and the other `:read` scopes), with a credit cap. The assistant reads call transcripts, which are text written by third parties; a key that reads them without supervision should not carry `calls:write` or `campaigns:write`. `ryvo mcp install` warns you when the current key does.
</Warning>
