<!-- Source: https://docs.geopera.com/api-reference/sdks/cli/commands · Markdown for LLMs -->

# CLI command reference

Every operation in the platform is available as its own `geopera <resource> <action>` command, generated directly from the published operation list, so `catalog.search` becomes `geopera catalog search` and request fields become first-class `--flags` you can discover with `--help`. This page is the complete reference for that command tree: how operation ids map to commands, how request-body fields map to flags (scalars, repeatable arrays, paired booleans, required markers), how the `--json` fallback composes with flags, the static commands (`login`, `logout`, `whoami`), shell completion, the low-level `geopera op` escape hatch, and the exit-code contract scripts can rely on. For installation, profiles, and the credential store, see the [CLI overview](/api-reference/sdks/cli); for the request model behind each command, see [operations](/api-reference/operations); for token formats, see [authentication](/api-reference/authentication).

## Synopsis

```bash
geopera [COMMAND] [SUBCOMMAND...] [OPTIONS]
```

Running `geopera` with no command prints help and lists the top-level resources. Commands fall into two groups:

| Group                        | Examples                                                 | Purpose                                      |
| ---------------------------- | -------------------------------------------------------- | -------------------------------------------- |
| Generated operation commands | `geopera catalog search`, `geopera orders archive place` | One command per operation, with typed flags. |
| Static commands              | `geopera login`, `geopera logout`, `geopera whoami`      | Authentication and identity.                 |

A third, hidden command — `geopera op` — invokes any operation by raw id and is documented below as an [escape hatch](#geopera-op-escape-hatch).

### Global options

Two options are shared by every command that talks to the API. Both fall back to environment variables, then to the value stored in the active profile, then to a built-in default.

| Option      | Env var           | Default                   | Description                                                                                              |
| ----------- | ----------------- | ------------------------- | -------------------------------------------------------------------------------------------------------- |
| `--profile` | `GEOPERA_PROFILE` | `default`                 | Stored identity to use. Profiles namespace separate credentials in `~/.config/geopera/credentials.json`. |
| `--api-url` | `GEOPERA_API_URL` | `https://api.geopera.com` | API base URL override. The trailing slash is stripped.                                                   |

API-URL resolution precedence (highest first): the `--api-url` flag, then `GEOPERA_API_URL`, then the `api_url` stored in the active profile, then `https://api.geopera.com`.

There is also a credential-free override for CI and short-lived shells:

| Env var             | Description                                                                                                                                                                                                                                                         |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `GEOPERA_API_TOKEN` | An opaque Bearer token or `gpra_` API key used directly, bypassing the credential store. When set, no profile lookup or token refresh happens — the value is sent as-is on the `Authorization` header. Use this in CI where you do not want to run `geopera login`. |

### Completion and help

These options are provided by the underlying CLI framework and live on the root command.

| Option                 | Description                                                                                  |
| ---------------------- | -------------------------------------------------------------------------------------------- |
| `--install-completion` | Install shell completion for the current shell and exit.                                     |
| `--show-completion`    | Print the completion script for the current shell (pipe it into your shell config) and exit. |
| `--help`               | Show help for `geopera` or any subcommand and exit.                                          |

```bash
# Install tab completion into your shell profile (bash, zsh, fish, PowerShell)
geopera --install-completion

# Or print the script and place it yourself
geopera --show-completion >> ~/.zshrc

# Per-command help — lists the flags generated for that operation
geopera catalog search --help
geopera orders archive place --help
```

Completion covers the full generated tree: once installed, `geopera ca<TAB>` completes to `catalog`, `geopera catalog <TAB>` lists the actions under `catalog`, and flag names complete after `--`. Because the tree is built from a spec snapshot bundled in the installed package, completion is offline and instant; it refreshes when you upgrade the CLI.

## The generated command tree

Every operation has a dotted id like `catalog.search` or `orders.tasking.templates.save`. The CLI turns each segment of that id into a level of the command tree, so the command is simply the id with spaces instead of dots:

| Operation id                    | Command                                 |
| ------------------------------- | --------------------------------------- |
| `catalog.search`                | `geopera catalog search`                |
| `orders.archive.place`          | `geopera orders archive place`          |
| `orders.tasking.templates.save` | `geopera orders tasking templates save` |

This mapping is mechanical and complete: any operation in the published snapshot has a matching command, and the command name always mirrors the operation id. There is nothing to memorise beyond the id itself.

A few properties of the tree are worth knowing:

- **No collisions.** Operation ids are guaranteed collision-free as a tree — no id is a prefix of another — so every id is an unambiguous leaf and no name is ever both a group and a runnable command.
- **Underscores become hyphens.** A segment like `federated_search` becomes the command `federated-search`, so `catalog.federated_search` is `geopera catalog federated-search`. The same rule applies to flag names (below).
- **Customer operations only.** Admin/internal operations (those requiring an `admin:` scope) are excluded from the generated tree. They remain reachable through the raw [`geopera op`](#geopera-op-escape-hatch) command for the rare privileged caller.
- **All operations are POST.** Every command issues `POST /v1/op/<operation_id>` with a JSON body. There are no GET-style commands; "reading" the catalog is itself a POST search.

### Fields become flags

The request body for each operation is exposed as flags, so for common calls you never write JSON by hand. The flag set is derived from the operation's request-body schema:

- **Scalar fields** (strings, integers, numbers) become `--flag value`. The flag name is the field name with underscores replaced by hyphens — a field named `host_name` becomes `--host-name`. Integers and floats are validated by the CLI, so `--limit abc` is rejected before any request is sent.
- **Array-of-scalar fields** are repeatable — pass the flag once per element: `--collections sentinel-2-l2a --collections landsat-c2-l2`. Omitting the flag entirely sends no value for that field (it is not sent as an empty array).
- **Boolean fields** become a paired switch: `--cloud-mask` sets it to `true`, `--no-cloud-mask` sets it to `false`. Omitting both leaves the field unset (the server default applies) — the switch is tri-state, not defaulted to `false`.
- **Required fields** are marked `[required]` in `--help` and must be supplied, either as a flag or via the `--json` body below.
- **Complex fields** (nested objects, arrays of objects, geometries, filter trees) are _not_ exposed as flags. They are only reachable via `--json`. `--help` lists exactly the flags that exist; if a field you need is missing from `--help`, it is a complex field — supply it through `--json`.

Field descriptions from the schema are carried through into `--help`, so each generated flag documents itself:

```bash
geopera catalog search --help
```

```
Search the catalog for imagery.

Operation: catalog.search

Options:
  --json JSON               Full JSON body (inline, @file, or - for stdin).
                            Flags override its keys.
  --collections TEXT        Collection ids to search. [required]
  --host-name TEXT          STAC host to query.
  --limit INTEGER           Maximum number of results.
  --profile TEXT            Stored identity (env: GEOPERA_PROFILE).
  --api-url TEXT            API base URL override (env: GEOPERA_API_URL).
  --help                    Show this message and exit.
```

A minimal call using only flags:

```bash
geopera catalog search --collections sentinel-2-l2a --limit 10
```

### Complex bodies: --json

Deeply nested fields (geometries, filter trees, line-item lists) are awkward as flags, so every generated command also accepts a full JSON body via `--json`, supplied three ways:

```bash
# Inline JSON
geopera catalog search --json '{"collections": ["sentinel-2-l2a"], "limit": 10}'

# From a file (@ prefix)
geopera catalog search --json @search.json

# From stdin (- )
cat search.json | geopera catalog search --json -
```

The body must be a JSON **object** — a top-level array or scalar is rejected with `Error: --json must be a JSON object for this command.` Malformed JSON is rejected with `Error: Invalid --json body: ...` before any request is sent. An empty or whitespace-only `--json -` is treated as `{}`.

#### Flags override --json keys

Flags and `--json` compose: when both are given, individual flags **override** the matching keys from `--json`. The body is built by first parsing `--json` into a dict, then layering each supplied flag on top. This lets you keep a base body in a file and tweak one value on the command line:

```bash
# Reuse a saved body but bump the limit for this run
geopera catalog search --json @search.json --limit 25
```

Precisely:

- A scalar flag with a value replaces that key in the parsed body.
- A repeatable flag, given at least once, replaces the whole array for that key (it does not append to a `--json` array).
- A boolean given as `--x`/`--no-x` replaces that key; omitting both leaves whatever `--json` provided.
- An omitted scalar/array flag (no value) does **not** touch the corresponding `--json` key — so partial overrides are safe.

### Worked examples across domains

Search the catalog, mixing a scalar flag with a repeatable array flag:

```bash
geopera catalog search \
  --host-name earthsearch-aws \
  --collections sentinel-2-l2a \
  --collections sentinel-2-l1c \
  --limit 10
```

Run a federated search across hosts (note the underscore-to-hyphen rule in the action name):

```bash
geopera catalog federated-search \
  --collections sentinel-2-l2a \
  --limit 25
```

Place an archive order whose body is a nested line-item list — supplied via `--json` because the items are objects:

```bash
geopera orders archive place --json @order.json
```

```json
{
	"items": [{ "product_id": "prod_s2_l2a", "scene_id": "S2A_...", "aoi_km2": 120 }]
}
```

Estimate the cost of a body before placing it, reading from stdin:

```bash
geopera orders estimate --json - < order.json
```

Save a tasking template, showing a deep command path, a scalar flag, a boolean switch, and a nested geometry via `--json` — with the `--name` flag overriding the `name` key in the file:

```bash
geopera orders tasking templates save \
  --name "Coastal weekly" \
  --recurring \
  --json @template.json
```

List your orders (a POST search that takes a body, even when "reading"):

```bash
geopera orders list --json '{"status": "active", "limit": 50}'
```

Every generated command prints pretty-printed JSON on success and renders an [errors](/api-reference/errors) response on failure (see the [exit-code contract](#exit-code-contract)). Output is unstyled JSON suitable for piping into `jq`:

```bash
geopera catalog search --collections sentinel-2-l2a --limit 5 \
  | jq '.features[].id'
```

## login

Authenticate the active profile. By default this runs a browser-based device flow; pass `--api-key` to store a key non-interactively for headless use.

```bash
geopera login [OPTIONS]
```

| Option         | Type   | Default                       | Description                                                                                                    |
| -------------- | ------ | ----------------------------- | -------------------------------------------------------------------------------------------------------------- |
| `--api-key`    | string | —                             | Skip the device flow and store an API key. Use `-` to read the key from stdin (keeps it out of shell history). |
| `--api-url`    | string | resolved (see global options) | API base URL override.                                                                                         |
| `--no-browser` | flag   | `false`                       | Do not auto-open the verification URL in a browser.                                                            |
| `--scope`      | string | `openid profile`              | Scope to request (device flow only).                                                                           |
| `--profile`    | string | resolved (see global options) | Profile to store the credentials under.                                                                        |

### Device flow (default)

With no `--api-key`, `login` runs a device authorization flow: it prints a verification URL and a short user code, opens the URL in your browser (unless `--no-browser` is set), and shows a spinner while it polls until you confirm in the browser. On success it stores the resulting **session token** for the profile and confirms with a `userinfo` round-trip.

```bash
geopera login
```

```
  To sign in, visit:
    https://api.geopera.com/device?user_code=WDJB-MJHT

  And confirm this code:
    WDJB-MJHT

Waiting for authorization... |
Logged in as user-123 (profile 'default').
```

On a headless machine, pass `--no-browser` and open the printed URL yourself:

```bash
geopera login --no-browser --profile workstation
```

The session token is short-lived and is refreshed automatically before expiry on subsequent commands, so you do not re-run `login` until the underlying session itself ends. The session token is sent as a normal Bearer token — it is used exactly like an API key, with no separate exchange step.

### API key (headless)

Pass `--api-key` to store a minted API key without any browser interaction — the right choice for CI and servers. New Geopera keys carry the `gpra_` prefix; the CLI validates the key with a `userinfo` call before saving it, so a bad key fails fast. A key without the `gpra_` prefix still works but prints a yellow notice on stderr.

```bash
# Read the key from stdin so it never appears in shell history
echo "$GEOPERA_KEY" | geopera login --api-key - --profile ci
```

```
Logged in as prn_org_8f2c... (API key, profile 'ci').
```

You can also pass the key inline (`geopera login --api-key gpra_...`), but piping via `-` is recommended so the key stays out of your shell history and process list. In ephemeral CI where you would rather not write to the credential store at all, skip `login` entirely and set `GEOPERA_API_TOKEN` (see [global options](#global-options)).

### Exit behaviour

`login` exits `0` on success. Any failure — no key provided, key validation failure, device-flow error — exits `1` with an `Error: ...` line on stderr.

## logout

Clear the active profile's stored credentials. For a device-flow (session) profile this also revokes the session server-side before deleting the local entry; for an API-key profile it simply removes the local entry (the key remains valid until you revoke it in the dashboard).

```bash
geopera logout [OPTIONS]
```

| Option      | Type   | Default                       | Description       |
| ----------- | ------ | ----------------------------- | ----------------- |
| `--profile` | string | resolved (see global options) | Profile to clear. |

```bash
geopera logout --profile ci
```

```
Logged out (profile 'ci').
```

If the profile had no stored credentials, it prints `No stored credentials for profile '...'.` and still exits `0`, so `logout` is safe to run unconditionally in teardown scripts.

## whoami

Show the authenticated principal, organisation, and scopes for the active profile. Output is a human-readable key/value listing by default, or raw `userinfo` JSON with `--json`.

```bash
geopera whoami [OPTIONS]
```

| Option      | Type   | Default  | Description                                                     |
| ----------- | ------ | -------- | --------------------------------------------------------------- |
| `--json`    | flag   | `false`  | Print the raw `userinfo` JSON instead of the formatted listing. |
| `--api-url` | string | resolved | API base URL override.                                          |
| `--profile` | string | resolved | Profile to inspect.                                             |

### Default (human) output

```bash
geopera whoami
```

```
sub:             user-123
principal_type:  user
org_id:          org_8f2c
scope:           openid profile catalog:read orders:write
api_url:         https://api.geopera.com
profile:         default
```

The fields map to `userinfo` keys: `sub`, `geopera_principal_type`, `geopera_org_id`, and `scope`. The `api_url` and `profile` lines reflect the resolved context, not the response body. Missing values render as `-`.

### Raw JSON output

```bash
geopera whoami --json
```

```json
{
	"sub": "user-123",
	"geopera_principal_type": "user",
	"geopera_org_id": "org_8f2c",
	"scope": "openid profile catalog:read orders:write"
}
```

Use raw output to check scopes from a script before attempting a scoped call:

```bash
geopera whoami --json | jq -e '.scope | contains("orders:write")'
```

### Exit behaviour

An auth failure (no stored credentials, refresh failure) or an API error exits `1` with an `Error: ...` line on stderr.

## geopera op — escape hatch

The generated command tree covers every customer operation in the published snapshot. For scripting against a dynamic operation id, for an operation newer than your installed CLI, or for a privileged (`admin:`) operation that is excluded from the tree, the low-level `geopera op` command invokes any operation directly by id:

```bash
geopera op <operation_id> [BODY] [OPTIONS]
```

It performs the same `POST /v1/op/<operation_id>` call as the generated command — there is no extra capability, only a generic entry point that takes a raw JSON body instead of typed flags.

| Argument / option | Description                                                                                             |
| ----------------- | ------------------------------------------------------------------------------------------------------- |
| `OPERATION_ID`    | The dotted operation id, e.g. `catalog.search` or `orders.estimate`. Required unless `--list` is given. |
| `BODY`            | Positional JSON request body. Use `-` to read from stdin. Omit to send `{}`.                            |
| `--file`, `-f`    | Read the JSON body from a file (alternative to the positional `BODY`).                                  |
| `--list`          | List available operation ids (fetched from the live OpenAPI document) and exit.                         |
| `--api-url`       | API base URL override (see global options).                                                             |
| `--profile`       | Stored identity to use (see global options).                                                            |

```bash
# Equivalent to: geopera catalog search --json '{"collections":["sentinel-2-l2a"],"limit":10}'
geopera op catalog.search '{"collections": ["sentinel-2-l2a"], "limit": 10}'

# Body from a file
geopera op orders.archive.place --file order.json

# Body from stdin
cat order.json | geopera op orders.archive.place -

# No body — sends {}
geopera op orders.list

# Discover ids from the live spec (one per line, count on stderr)
geopera op --list

# Drive the operation id from a variable in a script
for OP in catalog.search orders.list; do
  geopera op "$OP" '{}'
done
```

Unlike the generated tree (built from the bundled snapshot), `geopera op --list` queries the deployment's live `openapi.json`, so it reflects exactly what your configured `--api-url` currently serves — handy for spotting an operation that shipped after your CLI did.

Prefer the generated `geopera <resource> <action>` commands for everyday use — they give you typed flags, `--help`, and completion. Reach for `geopera op` only when you need a raw, id-driven call.

## Exit-code contract

The CLI uses exit codes to distinguish operation failures from everything else, so scripts can react accordingly.

| Code | Meaning                                                      | Emitted by                                                                          |
| ---- | ------------------------------------------------------------ | ----------------------------------------------------------------------------------- |
| `0`  | Success                                                      | all commands                                                                        |
| `1`  | Auth failure, bad input, or any non-operation error          | all commands (e.g. `login`, `logout`, `whoami`, invalid JSON, missing operation id) |
| `2`  | The operation call itself failed (the API returned an error) | generated operation commands and `op`                                               |

The distinction between `1` and `2` is deliberate: a setup problem (no credentials, malformed `--json`, a missing required field) exits `1`, while a request that reached the API and was rejected exits `2`. So an authentication failure exits `1` even on an operation command — only a failed operation invocation against the API yields `2`. Every error is written to stderr prefixed with `Error: `, combining the HTTP status with the problem `title`/`message`/`detail` from the [errors](/api-reference/errors) response:

```
Error: [422] Invalid request body — aoi_km2 must be greater than 0
```

Branch on the code in scripts:

```bash
geopera orders estimate --json @order.json
case $? in
  0) echo "estimate ok" ;;
  2) echo "API rejected the order body" >&2; exit 1 ;;
  *) echo "setup/auth error — check credentials and JSON" >&2; exit 1 ;;
esac
```

```bash
geopera orders estimate --json '{}'
echo "exit: $?"   # 2 if the API rejected the body, 0 on success
```

## See also

- [CLI overview](/api-reference/sdks/cli) — installation, profiles, environment variables, and the credential store.
- [Authentication](/api-reference/authentication) — Bearer tokens (`gpra_` API keys and session tokens).
- [Operations](/api-reference/operations) — the request model behind each command.
- [Errors](/api-reference/errors) — the problem+json schema rendered on failure.
