# Command-line interface

> The sightradar CLI for shells, scripts and coding agents: every API operation as a command, with --json, --dry-run and documented exit codes.

Source: https://sightradar.com/docs/sdks/cli

> **Coming soon.** `sightradar-cli` is not on npm yet, so `npm install -g sightradar-cli` will not work until it publishes. The commands, flags and exit codes on this page are the interface it ships with.

```bash
npm install -g sightradar-cli
sightradar --help
```

The package is `sightradar-cli` and the command it installs is `sightradar`. It needs Node.js 20 or newer and has no runtime dependencies. MIT licensed.

Every command has `--help` with examples, `--json` prints the API response exactly as returned, and `--dry-run` prints the request as a `curl` command without sending it. The [agent skill](/docs/agent-skill) teaches coding agents to use it.

## Log in

Create a key in the [console](/dashboard/keys). It is shown once. The CLI looks for a key in this order:

1. The `SIGHTRADAR_API_KEY` environment variable, the same one the SDKs read.
2. `~/.config/sightradar/config.json`, written by `sightradar login`. The directory gets mode 0700 and the file 0600. If `$XDG_CONFIG_HOME` is set, the file lives under it instead.

```bash
sightradar login                        # hidden prompt
printenv SR_KEY | sightradar login      # non-interactive: reads the key from stdin
sightradar whoami                       # where the key comes from, and a free check that it works
sightradar logout                       # removes the stored key (does not revoke it)
```

`login` reads the key from stdin and never from an argument, so the key stays out of shell history and process lists. It checks the key with a free `GET /v1/wallet` call before saving; pass `--no-verify` to skip that. If the stored file is readable by other users, every command warns you and prints the `chmod 600` fix.

In CI, set `SIGHTRADAR_API_KEY` from your secret store and skip `login`. The variable always wins over the stored key.

## Commands

| Command                                                                       | API                                                  | Credits      |
| ----------------------------------------------------------------------------- | ---------------------------------------------------- | ------------ |
| `collections create <id>`                                                     | `POST /v1/collections`                               | free         |
| `collections list [--filter t] [--limit n] [--offset n]`                      | `GET /v1/collections`                                | free         |
| `collections describe <id>`                                                   | `GET /v1/collections/{id}`                           | free         |
| `collections delete <id> [--immediate \| --compliance] [--yes]`               | `DELETE /v1/collections/{id}`                        | free         |
| `collections restore <id>`                                                    | `POST /v1/collections/{id}/restore`                  | free         |
| `collections deletion-status <id>`                                            | `GET /v1/collections/{id}/deletion`                  | free         |
| `index <collection> <image> [--photo-id id]`                                  | `POST /v1/collections/{id}/index`                    | 93           |
| `search <collection> <image> [--threshold 0-1] [--limit n]`                   | `POST /v1/collections/{id}/search`                   | 93           |
| `search-by-id <collection> <point-id>`                                        | `POST /v1/collections/{id}/search-by-id`             | 62           |
| `selfies register <collection> <image> --user-id u`                           | `POST /v1/collections/{id}/selfies`                  | 93           |
| `compare <source-url> <target-url>`                                           | `POST /v1/compare`                                   | 93 per image |
| `detect <image>`                                                              | `POST /v1/detect`                                    | 93           |
| `photos delete <collection> <photo-id> [--immediate \| --compliance] [--yes]` | `DELETE /v1/collections/{id}/photos/{photoId}`       | free         |
| `photos restore <collection> <photo-id>`                                      | `POST /v1/collections/{id}/photos/{photoId}/restore` | free         |
| `batch submit <collection> [urls...] [--file f] [--wait]`                     | `POST /v1/batches`                                   | 62 per photo |
| `batch status <batch-id> [--wait]`                                            | `GET /v1/batches/{id}`                               | free         |
| `batch photos <batch-id> [--all] [--status s]`                                | `GET /v1/batches/{id}/photos`                        | free         |
| `batch list [--limit n]`                                                      | `GET /v1/batches`                                    | free         |
| `balance`                                                                     | `GET /v1/wallet`                                     | free         |
| `usage [--days n]`                                                            | `GET /v1/usage`                                      | free         |

Credits follow [Billing](/docs/billing): per photo processed, never per face, and never for a `4xx` or `5xx`. Billable commands print the `X-Credits-Remaining` balance after they run.

The Rekognition names work as aliases: `index-faces`, `search-faces-by-image`, `search-faces`, `compare-faces`, `detect-faces`, `create-collection`, `list-collections`, `describe-collection`, `delete-collection` and `delete-faces`. `sightradar help <command>` is the same as `<command> --help`.

### Images

`<image>` is an `https://` URL that SightRadar fetches, a local file the CLI uploads as multipart, or `-` to read from stdin. `--gcs-key <key>` takes a Google Cloud Storage object instead. The limit is 30 MB, and the CLI checks local files before sending. `compare` and `batch submit` accept URLs (or GCS keys) only, because the API does not take uploads for them. See [Images](/docs/images).

### Collections

```bash
sightradar collections create event-2026
sightradar collections list --filter wedding
sightradar collections describe event-2026 --json | jq .face_count
```

### Index, search and selfies

```bash
sightradar index event-2026 ./photos/001.jpg --photo-id 001 --idempotency-key idx-001
sightradar search event-2026 ./selfie.jpg --limit 20 --threshold 0.6
sightradar search event-2026 ./selfie.jpg --json | jq -r '.photo_ids[]'

# Register the selfie once (93 credits), then each search by id costs 62
PID=$(sightradar selfies register event-2026 ./selfie.jpg --user-id u_42 --json | jq -r .point_id)
sightradar search-by-id event-2026 "$PID" --limit 50
```

`--threshold` is cosine similarity from 0 to 1, not Rekognition's 0 to 100. Calibrate it on your own photos with the [threshold guide](/docs/guides/thresholds). `search` also takes `--embedding-file <path>` to search with a precomputed 512-number vector.

### Compare and detect

```bash
sightradar compare https://example.com/id-photo.jpg https://example.com/selfie.jpg
sightradar compare --source-gcs-key kyc/u42/id.jpg --target-gcs-key kyc/u42/selfie.jpg
sightradar detect ./selfie.jpg                     # exactly one clear face?
curl -s https://cdn.example.com/g/001.jpg | sightradar detect -
```

### Batch

```bash
sightradar batch submit event-2026 --file urls.txt --wait
sightradar batch submit event-2026 https://cdn.example.com/1.jpg https://cdn.example.com/2.jpg
sightradar batch photos <batch-id> --all --status failed
sightradar batch list
```

`--file` reads one URL per line, `url,external_id` lines, or a JSON array of `{"url", "external_id"}` (`-` reads stdin). Blank lines and `#` comments are skipped. One submit takes at most 1,000 photos; the CLI refuses larger lists before sending and suggests `split -l 1000`. `--external-id-from-filename` sets each `external_id` from the URL's file name, `--op match` searches each photo instead of indexing it, and `--webhook whe_…` sends one signed delivery per photo. The CLI does not register webhooks; see [batch and webhooks](/docs/guides/batch-and-webhooks).

`--wait` polls the free status endpoint (every 5 seconds, `--poll-interval` to change) and prints progress to stderr.

### Delete and restore

```bash
sightradar collections delete event-2026                    # soft, restorable until purge_after
sightradar collections restore event-2026
sightradar collections delete tenant-42 --compliance --yes  # irreversible, audited erasure
sightradar collections deletion-status tenant-42            # done at phase=completed, verified_zero=true
sightradar photos delete event-2026 001                     # one photo's faces, restorable
```

Deletes are soft by default. `--immediate` skips the grace window and `--compliance` also records an audited erasure. Both are irreversible, so the CLI refuses to send either without `--yes`, and exits `2` with an explanation. `--dry-run` shows the request without needing `--yes`. See [deleting data](/docs/guides/deleting-data).

### Account

```bash
sightradar balance
sightradar usage --days 7 --json
```

## Machine-readable output

By default output is human-readable text on stdout. With `--json`, stdout gets the API response body exactly as returned. On failure, stdout is empty and stderr gets one JSON object:

```json title="stderr"
{
  "error": {
    "status": 402,
    "code": "insufficient_credits",
    "message": "…",
    "hint": "Your wallet is out of credits. Top up or enable auto-recharge…",
    "docs_url": "…"
  }
}
```

`hint` is always present and says what to do next. Branch on the exit code first and on `code` second.

## Preview a request with --dry-run

`--dry-run` prints the HTTP request as a `curl` command and exits `0`. Nothing is sent, nothing is charged, and **no API key is needed**, so you can check a command before you have an account. The key, if there is one, is shown masked and the printed command reads it from `$SIGHTRADAR_API_KEY`.

```bash
sightradar index event-2026 https://cdn.example.com/1.jpg --photo-id 1 --dry-run
```

```bash title="stdout"
# dry run: nothing was sent and nothing was charged
curl -X POST https://api.sightradar.com/v1/collections/event-2026/index \
  -H "Authorization: Bearer $SIGHTRADAR_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"url":"https://cdn.example.com/1.jpg","photoId":"1"}'
```

With `--json`, the preview is a JSON object with `dry_run`, `method`, `url`, `headers` and the body.

## Retries and idempotency

`--retries <n>` (default `2`, maximum `10`) retries `429`, `502` and `503` with jittered exponential backoff, and waits for `Retry-After` when the API sends it. Those statuses are never charged, so retrying them is always safe.

A network error is different: the request may have reached the API and been charged. The CLI retries network errors only for `GET` requests and for calls that carry `--idempotency-key`. Pass one on every billable call you might retry:

```bash
sightradar index event-2026 ./001.jpg --photo-id 001 --idempotency-key "idx-event-2026-001"
```

A replay under the same key returns `409` without a second charge. `selfies register` is the exception: a replay returns the stored `point_id` with `Idempotent-Replay: true`. See [core concepts](/docs/concepts#idempotency).

## Global options

| Option             | Default                                                | Meaning                                                                |
| ------------------ | ------------------------------------------------------ | ---------------------------------------------------------------------- |
| `--json`           | off                                                    | API response body on stdout; errors as JSON on stderr                  |
| `--dry-run`        | off                                                    | Print the request as curl and exit. No network call, no charge, no key |
| `--base-url <url>` | `$SIGHTRADAR_BASE_URL` or `https://api.sightradar.com` | API base URL                                                           |
| `--timeout <ms>`   | `60000`                                                | Per-request timeout                                                    |
| `--retries <n>`    | `2`                                                    | Retries on `429`/`502`/`503` (and network errors when safe)            |
| `-h`, `--help`     |                                                        | Help for the command, with examples                                    |
| `--version`        |                                                        | Print the CLI version                                                  |

## Exit codes

| Exit | Meaning                                                                                                                     |
| ---- | --------------------------------------------------------------------------------------------------------------------------- |
| `0`  | OK, including `--dry-run`                                                                                                   |
| `1`  | The API rejected the request (`400`, `404`, `409`, `413`), or another error                                                 |
| `2`  | Usage error: unknown command or option, missing argument, bad value, unreadable local file, unconfirmed irreversible delete |
| `3`  | Auth: no key, `401`, or `403`                                                                                               |
| `4`  | Out of credits (`402`)                                                                                                      |
| `5`  | Retryable: `429`, `502`, `503`, or a network error                                                                          |

```bash
sightradar search event-2026 ./selfie.jpg --json > out.json
case $? in
  0) jq -r '.photo_ids[]' out.json ;;
  4) echo "Top up credits first" ;;
  5) echo "Transient, retry later" ;;
  *) exit 1 ;;
esac
```

## Troubleshooting

Set `SIGHTRADAR_API_KEY`, or run `sightradar login`. `sightradar whoami --offline` shows where the CLI is looking without calling the API.

The variable wins over the stored key, so an old `SIGHTRADAR_API_KEY` in your shell overrides a fresh `login`. `sightradar whoami` prints the masked prefix and the source. Unset the variable or update it.

Run the `chmod 600` command the warning prints. `login` writes the file with mode 0600, so this usually means it was copied or edited by hand.

SightRadar fetches the URL itself, so it must be public and return an image. Signed URLs that have expired, pages behind a login, and HTML pages all fail. You are not charged for a `400`.

Batch takes public URLs only. Upload local files to storage first, or index them one at a time with `sightradar index`.

The `Idempotency-Key` was already used, so the original call already ran and was charged once. Do not retry with the same key. A `409` without a key on a collection command usually means the collection is deleting; check `collections deletion-status`.

Raise `--timeout`, or send a URL instead of a local file so SightRadar fetches the image. If a billable call timed out without `--idempotency-key`, check `sightradar usage` before retrying.

A `200` with empty `matches` carries a `reason`: `no_face`, `low_quality_selfie` or `point_not_found`. These searches are charged. Run `sightradar detect` on the selfie, or lower `--threshold`.
