# Rinzo CLI `rinzo` manages a Rinzo phone system from a terminal, a script or an AI agent: people and extensions, desk phones and softphones, numbers, call routing, voicemail, ring groups, call logs and webhooks to other systems. It drives the same Kazoo API as the Control panel (`https://app.rinzo.ai/v2`), so everything it does shows up there and stays editable there. Python 3.10 or newer; no other dependencies. ## Install ```sh curl -fsSL https://rinzo.ai/install.sh | sh ``` The script downloads the CLI from rinzo.ai, checks its SHA-256 and installs it with `uv`, `pipx` or a private virtualenv, putting `rinzo` in `~/.local/bin`. To do it yourself, download the wheel named in [`/downloads/SHA256SUMS`](https://rinzo.ai/downloads/SHA256SUMS), check it with `sha256sum -c SHA256SUMS`, then `uv tool install ./rinzo-*.whl` (or `pipx install …`). Rinzo was called Topaz. Coming from the `topaz` command? See [Upgrading from the topaz CLI](#upgrading-from-the-topaz-cli). ## Log in ```sh rinzo login ``` The CLI shows a link and a code such as `BCDF-GHJK`, and opens the browser. On that page (on app.rinzo.ai) sign in as an **account administrator** — or just confirm if you are already signed in to the Control panel — check the code and click **Approve**. The CLI then receives the account's API key directly from Rinzo and stores it: - in the operating system's keychain (macOS Keychain; GNOME Keyring or KWallet through `secret-tool` on Linux), or - where there is no keychain (servers, containers, Windows), in `~/.config/rinzo/credentials.json`, readable only by you. It exchanges the key for a one-hour session token, cached in the same directory. `rinzo whoami` shows the account and where the key is kept; `rinzo doctor` checks the whole chain. Other ways in: | Situation | Command | |---|---| | An AI agent (no terminal the person can type in) | `rinzo login --start`, show the person the link, then `rinzo login --wait --timeout 100` (exit code 4 = still waiting; run it again) | | Username and password instead of a browser | `rinzo login --password` (asks for username, account name, password); an administrator's sign-in, since other users can change only their own profile | | CI or a container, with a key from a secrets manager | set `RINZO_API_KEY` (and `RINZO_API_URL` for another server), or pipe it to `rinzo login --api-key-stdin` | | Several accounts | `rinzo login --profile acme`, then `--profile acme` on any command, or `rinzo profiles use acme` | **What the key can do.** It is the account's own Kazoo API key: it can do everything an account administrator can do in the Control panel, for as long as it exists. Keep it like a password. `rinzo logout` removes it from this computer only. `rinzo auth rotate-key --yes` issues a new key and stores it here: every other copy (other computers, integrations) stops working at once — that is how to revoke access. ## Give an AI agent the skill ```sh rinzo skill install # Claude Code: ~/.claude/skills/rinzo rinzo skill install --agent codex # Codex: ~/.codex/skills/rinzo (or --agent all, or --dir DIR) ``` The `rinzo` skill teaches an agent the safe way to work (look first, explain the plan, ask before anything that costs money or deletes data, verify with a test call), the Rinzo specifics, and step-by-step recipes: new employee, main number with an auto attendant and business hours, ring and hunt groups, forwarding, shared voicemail, conference bridge, blocked callers, call recording, call events to a CRM, offboarding. It ships with the CLI, so it always matches the installed version; `rinzo skill show` prints it. ## Commands `rinzo COMMAND --help` describes every option; `rinzo schema` lists all commands as JSON. | Command | What it manages | |---|---| | `users` | people: `create --first-name --last-name --email --extension auto --invite`, `invite`, `forward --to NUMBER`, `delete --yes` | | `devices` | phones: `create --name --owner USER --type sip_device\|softphone\|cellphone …`, `credentials ID`, `status` | | `numbers` | `list` (and where each routes), `assign N --user\|--callflow`, `unassign`, `release N --yes`; during early access new numbers come from support (`buy` is refused for customer accounts) | | `ring-groups` | `create --name --member ID … [--strategy single]`, `list`, `delete --yes` | | `calls` | `list --since 24h` (call history), `get ID`, `legs ID`, `active` | | `voicemail` | boxes, plus `messages BOX`, `message BOX MSG --download FILE` | | `callflows`, `menus`, `rules`, `rule-sets` | call routing, auto attendants, business hours and holidays | | `media` | greetings and prompts: `create --name --file greeting.wav`, `upload`, `download` | | `webhooks` | call events to your systems: `available`, `create --name --uri --event` | | `groups`, `conferences`, `faxboxes`, `directories`, `blacklists` | the remaining Kazoo resources | | `account`, `balance`, `registrations` | account settings, calling credit (read only), registered phones | | `api METHOD PATH` | any other Kazoo API call, relative to the account (`rinzo api GET limits`) | | `feedback` | `bug` or `feature`: report a problem or a missing feature to Rinzo (`--last-error` attaches the last failure) | Every resource command has `list [--filter FIELD=VALUE] [--limit N]`, `get ID`, `create`, `update ID` and `delete ID --yes`. `create` and `update` take a JSON document with `--data FILE` (`-` for stdin) and/or fields with `--set key=text`, `--set key:=json` (numbers, true/false, lists, objects) and dots for nesting; `update` reads the current document, merges your changes (`--unset key` removes one) and saves it whole. `users create`, `devices create`, `ring-groups create` and `users delete` write exactly what the Control panel's SmartPBX writes (a user's extension, voicemail box and phones belong together), so the result looks and behaves as if it had been set up there. ## Examples A new employee with extension, voicemail, a desk phone and their mobile ringing too: ```sh rinzo users create --first-name Jane --last-name Doe --email jane@example.com --extension auto --invite rinzo devices create --name "Jane desk phone" --owner USER_ID # prints the SIP settings for the phone rinzo devices create --name "Jane mobile" --owner USER_ID --type cellphone --number +16045551234 ``` A sales line that rings two people, on its own number: ```sh rinzo ring-groups create --name Sales --member USER_1 --member USER_2 rinzo numbers assign +16045550123 --callflow CALLFLOW_ID ``` Yesterday's calls, and call events pushed to a CRM: ```sh rinzo calls list --since 24h rinzo webhooks create --name CRM --uri https://crm.example.com/rinzo --event channel_destroy ``` Phones are configured with the device's SIP username and password, the account realm as SIP domain and `sip.rinzo.tel` as outbound proxy: UDP/TCP on port 5060, or TLS on 5061 for encrypted signalling (with `devices create --srtp`, the phone must also use SRTP). ## Output, errors and exit codes At a terminal results are tables; otherwise (pipes, scripts, agents) they are JSON — the API's `data`, never the session token. Force either with `--json` or `--table`. Errors go to stderr (JSON when stderr is not a terminal) with Kazoo's reason and, for invalid data, each rejected field. Exit codes: `0` ok, `1` error, `2` bad usage, `3` not logged in or key rejected, `4` login still waiting for approval. ## Safety - Reading is free. Deleting anything, buying or releasing a number, and `api … DELETE` need `--yes`; agents are told to get the person's explicit OK first. - A phone number costs $10 a month from calling credit (less when prepaid in the Control panel). During early access customer accounts can't order numbers themselves: email support@mail.rinzo.ai. Credit, cards and top-ups are managed only in the Control panel (My Account › Billing); the CLI only reads the balance and never handles card details. - Calls to 911 and other emergency numbers are not supported on Rinzo. - Kazoo limits how often a client may sign in; the CLI reuses its session token and waits and retries when Kazoo says "too many requests". A change that timed out may still have happened, so the CLI never repeats one by itself: read it back first. - Webhook and call-recording URLs must be public HTTPS addresses; Rinzo refuses private networks. - Only account administrators can change the phone system. The key from `rinzo login` acts as one; a `--password` login as another user gets 403 for anything but that user's own profile. ## Feedback (bugs and feature requests) Agents report problems and missing features to Rinzo themselves; the `rinzo` skill says when. One command, a JSON receipt: ```sh rinzo feedback bug --last-error --title "ring-groups create answers 500" --details "Sales group with 3 members; retried once" rinzo feedback feature --title "Buy numbers from the CLI" --details "The person needed a second 604 number" ``` - Every failed `rinzo` command is recorded (the command and the error, with secret values such as `--set password=…` removed) in `errors.json`; `--last-error` attaches it. Failures that look like Rinzo's fault (5xx, unexpected errors) print a ready-made `rinzo feedback bug` command. - `--dry-run` prints exactly what would be sent. Reports go to `/cli/v1/feedback` on the API's origin with this login's session, so Rinzo knows the account; without a working login they are sent anonymously. Rinzo removes secrets again on arrival and groups repeats of one problem. ## How browser login works `rinzo login` follows the OAuth device-authorization pattern (RFC 8628). The CLI asks `https://app.rinzo.ai/cli/v1/device/code` for a code, then polls `/cli/v1/device/token`. The approval page at `/cli/activate` runs on the Control panel's own origin and has no JavaScript. Approval works only for administrators of the account. The key is read with the approving administrator's own session, held only in memory for at most ten minutes, handed to the polling CLI once, and then forgotten. The page shows which computer asked (name, address, time) — approve only a login you started yourself. ## Files and environment `~/.config/rinzo/` (`%APPDATA%\rinzo` on Windows, `$XDG_CONFIG_HOME/rinzo` when that is set, or `$RINZO_CONFIG_DIR`): `config.json` (profiles: API URL, account, user, which key store), `tokens.json` (session tokens), `credentials.json` (keys, only without a keychain), `login.json` (a browser login in progress), `errors.json` (the last failed command, for `rinzo feedback --last-error`). All are private (0600, in a 0700 directory) and refused if another user could read or replace them. Keychain entries are filed under the service `rinzo-cli`, one per profile. | Variable | Effect | |---|---| | `RINZO_API_KEY` | use this account API key instead of a saved login (CI, containers) | | `RINZO_API_URL` | another API, e.g. a test server (default: the Rinzo API named at the top) | | `RINZO_PROFILE` | the saved login to use (like `--profile`) | | `RINZO_CONFIG_DIR` | keep the files above somewhere else | | `RINZO_CREDENTIAL_STORE=file` | keep keys in `credentials.json` even where a keychain exists | | `RINZO_PASSWORD` | the password for `rinzo login --password`, instead of a prompt | | `RINZO_SIP_PROXY` | the outbound proxy `devices credentials` shows (default `sip.rinzo.tel`) | | `RINZO_FEEDBACK_URL` | where `rinzo feedback` sends reports (default: the saved login's origin) | ## Upgrading from the topaz CLI Rinzo was called Topaz. The CLI's package was `topaz-tel` and its command `topaz` (version 0.3 and earlier); it is now the `rinzo` package and command (0.4 and later). Your account, its API key, your phones and their SIP settings stay as they are. The old CLI uses `https://app.topaz.tel/v2`, which will later redirect to the new address; the old CLI refuses redirects, so upgrade before then. 1. Remove the old package; this does not touch your login or settings. Use the line for how you installed it (the install script used `uv` if you had it, else `pipx`, else its own environment): ```sh uv tool uninstall topaz-tel pipx uninstall topaz-tel rm -rf ~/.local/share/topaz/venv ~/.local/bin/topaz # installed by the script without uv or pipx ``` The Rinzo install script removes a `uv` or `pipx` install of `topaz-tel` itself. 2. Install the Rinzo CLI and check that your login came across: ```sh curl -fsSL https://rinzo.ai/install.sh | sh rinzo whoami ``` 3. Give your agents the new skill (it replaces `topaz-tel`), with the same options you used before: ```sh rinzo skill install # or --agent codex, --agent all, --dir DIR ``` 4. In scripts and CI, change `topaz` to `rinzo` and `TOPAZ_…` variables to `RINZO_…`. What happens by itself: - **Settings.** The first `rinzo` command that needs them, when `~/.config/rinzo` does not exist yet, copies `config.json`, `credentials.json`, `tokens.json` and `login.json` from `~/.config/topaz` (`%APPDATA%\topaz` on Windows), with the same checks as always (your own files, private, no symlinks) and the same 0600/0700 permissions, and says so on stderr. `~/.config/topaz` is never changed or deleted: remove it yourself once `rinzo` works. Nothing is copied when you set `RINZO_CONFIG_DIR` or `TOPAZ_CONFIG_DIR`; that directory is used as it is. - **API address.** A profile on exactly the old default, `https://app.topaz.tel/v2`, moves to the new default in the copy. Any other address (a test server, a local stack) is kept. - **Keychain.** A key filed under the service `topaz-cli` is copied to `rinzo-cli` the first time `rinzo` needs it, and read back to check it. The `topaz-cli` entry stays; `rinzo logout` removes both. - **Environment.** `TOPAZ_API_KEY`, `TOPAZ_API_URL`, `TOPAZ_PROFILE`, `TOPAZ_CONFIG_DIR`, `TOPAZ_CREDENTIAL_STORE`, `TOPAZ_PASSWORD` and `TOPAZ_SIP_PROXY` still work while the `RINZO_…` variable is not set, with one line on stderr naming the new names. - **The `topaz` command.** The `rinzo` package still installs `topaz` as an alias. It runs `rinzo` after one notice on stderr (stdout, and so JSON output, is unchanged) and will be removed in a later release. - **Skill.** `rinzo skill install` deletes a `topaz-tel` skill in the same skills directory when the topaz CLI installed it (it has the topaz CLI's `.installed-by-topaz-cli` marker). A `topaz-tel` directory without that marker (one you made yourself) is left alone, and the command says so. ## Accounts and billing A person creates the account at https://signup.rinzo.ai/, paying on Stripe's hosted checkout, and adds credit or changes the card in the Control panel under My Account › Billing (https://app.rinzo.ai/#/billing), in Stripe's own card form. An agent may share those links but must never enter, request or handle card details. ## Machine-readable files - https://rinzo.ai/agents/rinzo/SKILL.md: the rinzo agent skill (references in /agents/rinzo/references/) - https://rinzo.ai/agents/cli-schema.json: every command and option (`rinzo schema`) - https://rinzo.ai/agents/native-catalog.json: Kazoo API callback catalog (reference, not runtime coverage) - https://rinzo.ai/install.sh and https://rinzo.ai/downloads/rinzo-0.4.2-py3-none-any.whl: the CLI, version 0.4.2