# CLI

Use the preview Kirora CLI to manage API resources from your terminal, with saved logins, pagination and safe retries.

Use the `kirora` CLI to manage contacts, events, campaigns and other API resources from your terminal. Commands use the [Node.js library](/docs/sdks/node) for authentication, retries and error handling. The CLI is in preview and not yet published to npm.

```bash
kirora login                      # paste an API key; it is checked, then saved
kirora groups create --name VIPs
kirora contacts list --limit 100 --paginate | jq -r .email
kirora contacts identify --external-id cust_1042 --email ada@example.com --email-consent
```

## Install

The package is `@kirora/cli`, for Node.js 22.13 or later. It is not on npm yet.

## Sign in

`kirora login` asks for an API key (`kirora_…`) at a masked prompt. It checks the key against [`GET /v1/me`](/docs/api/reference/getMe) before saving it, so a mistyped key fails straight away. In a script, pipe the key in instead:

```bash
kirora login --profile staging    # a second login, used with --profile staging
kirora login < key.txt            # the key from stdin
kirora me get                     # check which key and workspace you are using
```

The key is never taken as a flag, where it would land in your shell history. Logins are saved in `$XDG_CONFIG_HOME/kirora/config.json` (by default `~/.config/kirora/config.json`), readable only by you. At login, `--base-url` saves another API host with the profile (without it, `KIRORA_API_URL` is used when set), and `--workspace` saves a partner key's tenant.

`kirora logout` removes the default login; `kirora logout --profile staging` removes a named one.

The key's scopes decide what the CLI may do. See [Authentication](/docs/api/authentication).

### Where the key comes from

Each call takes the first of these that is set:

1. The profile named by `--profile`, or by the `KIRORA_PROFILE` environment variable.
2. The `KIRORA_API_KEY` environment variable, for CI and scripts, which need no login. `KIRORA_API_URL` sets another host (the default is `https://api.kirora.io`) and `KIRORA_WORKSPACE` a partner key's tenant.
3. The default profile saved by `kirora login`.

## Commands

Every command is `kirora <resource> <action>`, and there is one for each operation in the [API reference](/docs/api/reference): [CLI commands](/docs/sdks/cli/commands) lists them all. The resource is the first part of the path (`/v1/sending-domains/…` is `sending-domains`), and the action is what the operation does:

```bash
kirora groups list
kirora groups get 3f1c2a9e-0000-4000-8000-000000000000
kirora sending-domains check 3f1c2a9e-0000-4000-8000-000000000000
kirora campaigns send 01a1c7e2-0000-4000-8000-000000000000
```

The help goes three levels deep. `kirora --help` lists the resources, `kirora groups --help` a resource's actions, and `kirora groups create --help` an action's flags and the scope its key needs. A mistyped command gets a suggestion.

### Arguments and flags

- **Ids in the path** are arguments, in order: `kirora templates get-version <id> <version>`.
- **Query parameters and simple body fields** are flags, named as in the API reference in kebab case: `externalId` is `--external-id`. A flag with a fixed set of values lists them in its help, and a number flag refuses anything else.
- **True or false** fields are a pair: `--email-consent` and `--no-email-consent`. Leave both out and the field isn't sent.
- **Lists of text** take several values: `--allowed-origins https://example.com https://www.example.com`.
- **Anything nested** goes in `-d` (`--data`) as the whole body: inline JSON, a file (`@file`) or stdin (`-`). Flags set fields on top of it.
- **A file upload** takes the file's path: `kirora assets upload --file logo.png`.

```bash
kirora transactional send -d @message.json --idempotency-key order-1001 --send-at 2026-11-02T09:00:00Z
```

### Sends and events

`events track`, `transactional send`, `campaigns trigger` and `push send` require `--idempotency-key`. Tie the key to what you are sending, such as an order number, and reuse it if you run the command again. See [Idempotency](/docs/api/idempotency).

### Lists

A list returns one page. On a list action, `--paginate` follows the cursors for you and prints each item as one line of JSON (JSON Lines), ready for `jq`. A larger `--limit` means fewer requests. See [Pagination](/docs/api/pagination).

### Flags on every command

| Flag | |
| --- | --- |
| `--profile <name>` | Use a saved login other than the default. |
| `--workspace <externalId>` | A partner key's tenant, sent as `Kirora-Workspace`: `--workspace tenant-42`. |
| `-i`, `--include` | Print the HTTP status and headers before the body. |

## Output and exit codes

A success prints the response body on stdout as the API sent it: pretty-printed, or one line per item with `--paginate`. `-i` prints the status and headers on a failure too.

An API error goes to stderr as `{ "error": { code, message, details, requestId } }`, so a pipeline only sees the body of a success. See [Errors](/docs/api/errors) for the codes.

| Exit code | Meaning |
| --- | --- |
| `0` | The call succeeded. |
| `1` | The call failed: an API error, a network failure, or no key to use. |
| `2` | The CLI could not read the command, and nothing was sent: an unknown command or flag, a missing id or idempotency key, a value that is not a number, or a body that is not valid JSON. |

## Retries

Retries follow the [Node.js library](/docs/sdks/node#retries-and-timeouts): a 429 is always retried, and other failures only when the call is safe to repeat. A create without an idempotency key is never retried, so it can't run twice.

## Agent keys

The CLI works with any API key, including an agent key, which drafts but never sends: see [Agents](/docs/api/agents) for what it may do. For AI assistants, prefer the hosted MCP server over shelling out to the CLI; it takes the same key.