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

# CLI

`geopera` is the official command-line interface for the platform — a structured command tree where every operation is its own command, with typed `--flags` derived from the request schema and a `--json` fallback for complex bodies.

## Overview

The CLI is a thin auth-and-dispatch shell over the typed [`geopera` Python SDK](/api-reference/sdks/python). It does two things and does them well: it manages your credentials (sign-in, token refresh, the choice between a bearer token and an API key), and it dispatches every platform operation. There is no per-command business logic — each command is generated from the platform's operation schema — so the surface stays in lock-step with the API and a newly shipped operation is callable the moment your snapshot includes it.

Two layers sit on top of that dispatch core:

- A **structured command tree** that mirrors the operation namespace directly into commands. An operation id like `catalog.search` becomes `geopera catalog search`; `orders.archive.place` becomes `geopera orders archive place`; `orders.tasking.templates.save` becomes `geopera orders tasking templates save`. The shape of the tree is exactly the shape of the [Operations reference](/api-reference/operations). This is the recommended way to use the CLI: discoverable, typed, and tab-completable.
- A **low-level escape hatch**, `geopera op <operation_id>`, that dispatches any operation id with a raw JSON body. It is hidden from `--help` but always available — useful for scripting where the operation id is dynamic, or to reach an operation that is not yet in your snapshot.

Each command in the tree exposes the fields of its request body. Scalar fields are plain `--flags`; array-of-scalar fields are repeatable; boolean fields are paired `--x` / `--no-x`; and fields marked `[required]` in `--help` must be supplied. Anything more complex — nested objects, arrays of objects, free-form geometry — is supplied with `--json`, and any `--flags` you add override matching keys from that JSON.

Every operation is a `POST`. The CLI does not expose, and the API does not offer, "idiomatic GET routes" — there is one verb and the variation lives entirely in the request body.

The package is **0.2.0** and currently in **Beta**.

## Install

```bash
pip install geopera-cli
```

This pulls in [`typer`](https://typer.tiangolo.com/), [`httpx`](https://www.python-httpx.org/), [`click`](https://click.palletsprojects.com/), and the [`geopera` SDK](/api-reference/sdks/python) (`>=2.0.0`). It requires **Python 3.11+** (tested on 3.11, 3.12, and 3.13).

Installation registers a single console command, `geopera`:

```bash
geopera --help
```

Running `geopera` with no arguments also prints help, so the entry point is self-describing.

## Hello, world

Sign in once, confirm who you are, then run an operation as a command:

```bash
# Sign in (opens your browser; RFC 8628 device flow)
geopera login

# Who am I?
geopera whoami

# Search a catalog
geopera catalog search --collections sentinel-2-l2a --limit 10
```

`login` establishes a credential under your active profile; every other command loads that credential and carries it to its operation.

## How the command tree is built

The structured tree is generated from a **spec snapshot** — a `spec.json` file bundled inside the installed package — rather than fetched at runtime. That makes the tree offline, instant, and tab-completable, and it refreshes with each release of the CLI. For each `POST /v1/op/{operation_id}` path in the snapshot the generator:

1. Splits the operation id on `.` into namespace segments — `orders.archive.place` becomes the groups `orders` then `archive` and the leaf command `place`.
2. Reads the operation's JSON request-body schema and turns each **scalar or array-of-scalar** field into a flag. Strings, integers, numbers, and booleans become typed flags; arrays of those become repeatable flags. Nullable fields (modeled as `anyOf: [<real>, {type: null}]`) are collapsed to their underlying type, so they flag cleanly.
3. Leaves every **complex** field (nested objects, arrays of objects) off the flag list — those are reachable only through `--json`.
4. Adds the universal `--json`, `--profile`, and `--api-url` options to every leaf.

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 command.

Operations that require an `admin:*` scope are **excluded** from the generated tree — they are internal and stay reachable only through the [`op` escape hatch](#low-level-escape-hatch) for the rare privileged caller. Everything customer-facing is in the tree.

## Run operations as commands

Each command surfaces its request fields as flags. Scalars become `--flags`, arrays are repeatable, and booleans toggle with `--x` / `--no-x`:

```bash
# Scalar + repeatable-array flags
geopera catalog search \
  --collections sentinel-2-l2a \
  --collections landsat-c2-l2 \
  --limit 10
```

Field names are mapped to flags by replacing underscores with dashes, so a request field `item_id` is supplied as `--item-id`. Run any command with `--help` to see its fields, which are required, and their types:

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

Required fields are annotated `[required]` in their help text. Each leaf's help also prints the underlying `Operation: <id>` so you can always cross-reference the [Operations reference](/api-reference/operations).

### Deeply nested operations

The command tree goes as deep as the namespace. Placing an archive order is `geopera orders archive place`:

```bash
geopera orders archive place \
  --item-id S2B_36MYE_20240101_0_L2A \
  --product analytic
```

### Complex bodies with --json

When a request has a large or nested body — or a field the generator left off the flag list because it is a nested object — pass the body as JSON instead of (or alongside) flags. `--json` accepts an inline string, a file with `@`, or stdin with `-`:

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

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

# from stdin (composes with jq, heredocs, other tools)
echo '{"collections": ["sentinel-2-l2a"]}' | geopera catalog search --json -
```

Flags override keys from `--json`, so you can keep a base request in a file and tweak one field on the command line. The JSON is parsed first into the request body, then any flags you set are written over the top:

```bash
geopera catalog search --json @base-search.json --limit 50
```

For a leaf command, `--json` must resolve to a JSON **object** (the request body). Passing a JSON array or scalar to a leaf is an error; the raw [`op`](#low-level-escape-hatch) command is the place for non-object bodies.

### Output and exit codes

Output is always pretty-printed JSON on stdout, so the CLI composes cleanly with `jq` and shell pipelines. On failure the platform's [RFC 9457](https://www.rfc-editor.org/rfc/rfc9457) `application/problem+json` response is rendered as a readable `[status] message — detail` line on stderr and the process exits non-zero:

| Exit code | Meaning                                                                                            |
| --------- | -------------------------------------------------------------------------------------------------- |
| `0`       | Success.                                                                                           |
| `1`       | Local/usage error — auth not configured, unreadable `--json` file, malformed JSON, bad invocation. |
| `2`       | Operation error — the platform returned a `problem+json` response (4xx/5xx).                       |

See [Errors](/api-reference/errors) for the full problem+json taxonomy.

## Commands

| Command                               | What it does                                                                          |
| ------------------------------------- | ------------------------------------------------------------------------------------- |
| `geopera login`                       | Authenticate. Device flow by default; `--api-key KEY` stores a key for headless use.  |
| `geopera logout`                      | Clear the active profile's stored credentials (best-effort logout).                   |
| `geopera whoami`                      | Show the authenticated principal, org, and scopes (validates the session).            |
| `geopera <resource> <action> [flags]` | Run an operation as a command, with schema-derived `--flags` and a `--json` fallback. |
| `geopera orders list`                 | Curated alias over `orders.list` with table-formatted output.                         |
| `geopera op OPERATION_ID [JSON]`      | Low-level escape hatch (hidden): dispatch any operation id with a raw JSON body.      |

Two global options are accepted on every command (including each generated leaf):

| Flag             | Env               | Default                   |
| ---------------- | ----------------- | ------------------------- |
| `--profile NAME` | `GEOPERA_PROFILE` | `default`                 |
| `--api-url URL`  | `GEOPERA_API_URL` | `https://api.geopera.com` |

The full reference for each command and flag lives on [Commands](/api-reference/sdks/cli/commands).

## Authenticate

`geopera login` runs the OAuth 2.0 Device Authorization Grant ([RFC 8628](https://www.rfc-editor.org/rfc/rfc8628)) with PKCE. It generates a PKCE verifier/challenge pair, requests a device and user code, prints the user code, opens the verification URL in your browser, and polls until you approve:

```bash
geopera login

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

Once approved, the resulting **session token** (and its refresh token) are stored under the active profile. The CLI confirms the sign-in with a `userinfo` round-trip and prints who you are. Session tokens are then refreshed automatically — proactively when within 30 seconds of expiry, and reactively on any `401`. The platform rotates the refresh token, so both tokens are rewritten on each refresh.

Two flags shape the device flow:

```bash
# Print the code but do not auto-open a browser (headless terminals).
geopera login --no-browser

# Request a specific OAuth scope.
geopera login --scope "openid profile"
```

### API keys (headless and CI)

For CI and other non-interactive use, store a minted API key instead of signing in through a browser. Keys carry the `gpra_` prefix and are sent on every authenticated request as the `X-API-Key` header:

```bash
geopera login --api-key gpra_xxxxxxxx

# keep the key out of shell history by piping it in:
printf '%s' "$GEOPERA_KEY" | geopera login --api-key -
```

`login --api-key` validates the key with a `userinfo` round-trip before saving it, so a bad key fails fast. The API key **is** the credential — there is no token exchange, no password grant, and no refresh step for keys; the value you store is the value that authenticates each request. (A browser sign-in instead yields a short-lived session token, used the same way.)

Both credential types resolve to the same principal model — scopes, audit, and provenance are identical. See [Authentication](/api-reference/sdks/cli/authentication) for the device flow, key storage, and the credential store, and the platform [Authentication](/api-reference/authentication) and [Scopes](/api-reference/scopes) references for how credentials are minted and what they authorise.

### whoami

`geopera whoami` validates the active credential and prints the resolved principal. Add `--json` for the raw userinfo document:

```bash
geopera whoami

# sub:             usr_01H8...
# principal_type:  user
# org_id:          org_01H8...
# scope:           openid profile
# api_url:         https://api.geopera.com
# profile:         default
```

## Low-level escape hatch

`geopera op` is a hidden, generic command that dispatches any operation id with a raw JSON body. Use it for scripting where the operation id is dynamic, to reach an `admin:*` operation excluded from the tree, or to call an operation that is not in your CLI's snapshot. The structured `geopera <resource> <action>` form is preferred for everyday use.

The body is JSON, supplied as a positional argument, from a file with `--file`, or from stdin with `-`:

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

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

# from stdin (composes with jq, heredocs, other tools)
echo '{}' | geopera op orders.list -
```

Operations with no required body can be called bare — an empty body defaults to `{}`:

```bash
geopera op orders.list
```

### Discover operations

`--list` enumerates every operation id from the live OpenAPI document, so the list always matches the deployment you are pointed at:

```bash
geopera op --list
```

This prints one operation id per line on stdout (with a `N operations.` count on stderr), making it easy to filter:

```bash
geopera op --list | grep '^orders\.'
```

The mechanics of body resolution, exit codes, and listing are detailed on [op](/api-reference/sdks/cli/op).

## Profiles and configuration

Multiple identities are namespaced by **profile**. Each is stored under its own key in the credential store and selected with `--profile` or `GEOPERA_PROFILE`:

```bash
geopera login --profile staging --api-url https://staging.api.geopera.com
GEOPERA_PROFILE=staging geopera whoami
geopera whoami --profile staging          # equivalent
```

Credentials live in `~/.config/geopera/credentials.json` (directory `0700`, file `0600`). Each top-level key is a profile holding its `api_url` and an `auth` block — `{"type": "oauth", ...}` for a session token, or `{"type": "api_key", "api_key": "gpra_..."}` for a key.

The API base URL resolves in this order: `--api-url` flag → `GEOPERA_API_URL` → the profile's stored `api_url` → `https://api.geopera.com`. For ephemeral CI use, `GEOPERA_API_TOKEN` supplies a session token or API key directly, bypassing the store entirely; a value starting with `gpra_` is treated as an API key. See [Configuration](/api-reference/sdks/cli/configuration) for the full precedence rules and store format.

## Worked example: search, then estimate

A complete two-step flow — search a catalog, pull a result id out with `jq`, then price an estimate — using structured commands and standard shell tooling:

```bash
# 1. Authenticate once (CI-style, no browser).
export GEOPERA_API_TOKEN="gpra_..."

# 2. Search the catalog and keep the first matching item id.
ITEM=$(geopera catalog search \
  --collections sentinel-2-l2a \
  --limit 1 | jq -r '.features[0].id')

# 3. Price an order against that item.
geopera orders estimate --item-id "$ITEM" --product analytic
```

Because `GEOPERA_API_TOKEN` is set, no `login` step is required and nothing is written to disk — ideal for ephemeral runners. More patterns, including masked secrets and matrix jobs, are on [CI recipes](/api-reference/sdks/cli/ci-recipes).

## Gotchas

- **Underscores become dashes.** A request field `max_cloud_cover` is the flag `--max-cloud-cover`. Use `--help` on the leaf to see the exact flag names.
- **Nested fields have no flag.** If a field is a nested object or an array of objects it will not appear as a `--flag`; supply it through `--json`. Mixing the two is fine — flags win on key collisions.
- **`--json` on a leaf must be an object.** Non-object bodies (arrays, scalars) are only valid through the raw `op` command.
- **Admin operations aren't in the tree.** Operations gated behind `admin:*` scope are reachable only via `geopera op`; you will not find a `geopera <resource> <action>` form for them.
- **The tree is a snapshot.** It reflects the spec bundled with your installed CLI version. To pick up brand-new operations as named commands, upgrade the package — or reach them immediately with `geopera op` and `geopera op --list`.

## Explore

<CardGrid columns={2}>
  <InfoCard title="Authentication" icon={KeyRound} description="Device flow, API keys, and the credential store." href="/api-reference/sdks/cli/authentication" />
  <InfoCard title="Configuration" icon={Settings2} description="Profiles, environment overrides, and URL precedence." href="/api-reference/sdks/cli/configuration" />
  <InfoCard title="op" icon={Send} description="The low-level escape hatch: raw bodies, listing, exit codes." href="/api-reference/sdks/cli/op" />
  <InfoCard title="Commands" icon={ListTree} description="The full reference for login, whoami, logout, and the command tree." href="/api-reference/sdks/cli/commands" />
  <InfoCard title="CI recipes" icon={Workflow} description="Headless auth and operation dispatch in pipelines." href="/api-reference/sdks/cli/ci-recipes" />
  <InfoCard title="Python SDK" icon={Terminal} description="The typed library the CLI is built on." href="/api-reference/sdks/python" />
</CardGrid>

## Links

- PyPI: [pypi.org/project/geopera-cli](https://pypi.org/project/geopera-cli/)
- Source: [github.com/geo-pera/geopera-cli](https://github.com/geo-pera/geopera-cli)
