CLI authentication & login
The geopera CLI signs in with the OAuth 2.0 device flow by default, or with a gpra_ API key for headless use, stores credentials in a per-profile file, and chooses automatically between an Authorization: Bearer token and an X-API-Key header on every request.
How the CLI handles auth
The CLI is a thin auth-and-dispatch shell over the published geopera SDK. The only CLI-resident logic is authentication: the device flow, token refresh and rotation, and choosing between a Bearer token and an X-API-Key header. Everything else is reached through structured commands that mirror each operation — for example geopera catalog search or geopera orders archive place. For the underlying token model — session tokens, minted gpra_ API keys, and how the API validates them — see Authentication.
There are exactly five auth-related commands, and they are the only commands the CLI implements itself:
| Command | What it does |
|---|---|
geopera login | Sign in via the device flow, or store an API key with --api-key. |
geopera logout | Clear the active profile’s stored credentials (and revoke an OAuth session). |
geopera whoami | Resolve the active profile and print the authenticated principal. |
(login, logout, and whoami are the three you will use daily; every other geopera <resource> <action> command is a structured wrapper over an operation, and the hidden geopera op is the raw escape hatch.)
Interactive login (default)
Running geopera login with no flags starts the OAuth 2.0 Device Authorization Grant (RFC 8628) with PKCE. This is the right choice on a workstation where a browser is available.
geopera loginThe flow is:
- The CLI generates a PKCE verifier and an
S256challenge, then calls the device-authorization endpoint as a public native client (client idgeopera-cli, no secret). - It prints a verification URL and a short user code, and — unless
--no-browseris set — opens the URL in your default browser. The CLI prefers the server’sverification_uri_complete(the URL with the code already embedded) and falls back to the bareverification_uri. - You confirm the code in the browser. Meanwhile the CLI polls the token endpoint on the server-supplied
interval(default 5 seconds), backing off by 5 seconds whenever the server repliesslow_down, and treatingauthorization_pendingas “keep waiting”. - On success it stores the access and refresh tokens, then confirms with a
userinforound-trip and prints the resolved principal.
Typical output:
To sign in, visit:
https://api.geopera.com/device?user_code=WDJB-MJHT
And confirm this code:
WDJB-MJHT
Logged in as 3f6c1b2a-... (profile 'default').While it polls, a small spinner renders on stderr (only when stderr is a TTY), so the spinner never pollutes piped or redirected output.
Login options
| Option | Default | Description |
|---|---|---|
--no-browser | off | Do not auto-open the verification URL. Print the code and URL only. |
--scope <scope> | openid profile | OAuth scope to request during the device flow. |
--api-key <key> | — | Skip the device flow and store an API key (see Headless login). Use - to read from stdin. |
--api-url <url> | https://api.geopera.com | API base URL override (env: GEOPERA_API_URL). |
--profile <name> | default | Stored identity to write to (env: GEOPERA_PROFILE). |
Use --no-browser on a remote box where the browser you want is on a different machine — copy the URL across yourself:
geopera login --no-browserRequest a different scope set with --scope (space-separated, quoted):
geopera login --scope "openid profile orders:write"The scope you request is stored alongside the token and reported by geopera whoami. For the full list of what each scope grants, see Scopes.
Device-flow timing and termination
The CLI honours the three timing fields the server returns from the device-authorization step:
| Field | Meaning | CLI behaviour |
|---|---|---|
interval | Seconds to wait between polls | Sleeps this long between token polls (default 5 if omitted). |
expires_in | Lifetime of the device request | Used as the polling deadline (default 600, about 10 minutes). |
verification_uri_complete | URL with the code pre-filled | Opened in the browser and shown first; falls back to verification_uri. |
The poll loop ends in one of four ways:
- Success — the token endpoint returns an access/refresh token pair; the CLI stores it and prints
Logged in as .... - Denied — you reject the request in the browser; the CLI reports
Login was denied in the browser.and exits non-zero. - Expired — the device request lifetime elapses before you confirm; the server returns
expired_token, or the CLI hits its own deadline and reportsLogin timed out before authorization completed.Rerungeopera login. - Unsupported — if the device-authorization endpoint itself is unavailable, the CLI reports
Device authorization failed. The server may not support the device flow yetand suggestsgeopera login --api-key <key>.
Headless login (API key)
For CI, servers, and other non-interactive contexts, skip the device flow and store a minted API key with --api-key. Geopera API keys start with the gpra_ prefix, and the key itself is the credential — there is no token exchange, OIDC handshake, or refresh step for API keys.
geopera login --api-key gpra_xxxxxxxxxxxxxxxxxxxxxxxxTo keep the key out of your shell history, pass - and feed it on stdin:
echo "$GEOPERA_API_KEY" | geopera login --api-key -The CLI validates the key with a userinfo call before storing it. If validation succeeds it prints the resolved principal id:
Logged in as svc_8a1c... (API key, profile 'default').If validation fails the key is not saved and the command exits non-zero:
Error: API key validation failed: ...An empty value (for example an empty stdin pipe) is rejected before any network call with Error: No API key provided.
A key without the gpra_ prefix still works, but the CLI prints a note to stderr that new Geopera keys are expected to start with gpra_:
Note: this key has no 'gpra_' prefix. It will still work, but new Geopera keys are expected to start with 'gpra_'.To learn how to mint keys and which scopes they carry, see Authentication.
How keys are sent
API keys are sent in the X-API-Key header with no prefix — not as a Bearer token. The CLI selects the header automatically based on how you logged in:
| Credential type | Header sent |
|---|---|
| OAuth (device flow / browser sign-in) | Authorization: Bearer <access_token> |
API key (gpra_...) | X-API-Key: gpra_... |
This selection is made per request from the stored credential type, so you never set the header yourself.
Ephemeral tokens without storing
To use a token for a single invocation without writing it to the credential file — handy in CI — set GEOPERA_API_TOKEN. The CLI inspects the value:
- A value starting with
gpra_is treated as an API key and sent asX-API-Key. - Any other value is treated as an opaque session token and sent as
Authorization: Bearer ....
export GEOPERA_API_TOKEN="gpra_xxxxxxxxxxxxxxxxxxxxxxxx"
geopera whoamiThis override takes precedence over any stored profile, is never persisted, and never refreshes (the CLI has no refresh token for it, so it is used verbatim). It is the cleanest way to run the CLI on a host that should hold no long-lived state on disk.
Inspecting the current principal
geopera whoami resolves the active profile (refreshing the OAuth token first if needed) and prints the authenticated principal, org, and scopes from a live userinfo call.
geopera whoamisub: 3f6c1b2a-9a4e-4c0f-8b21-1d2e3f4a5b6c
principal_type: user
org_id: org_8a1c...
scope: openid profile
api_url: https://api.geopera.com
profile: defaultThe fields map directly onto the userinfo payload:
| Line | Source field | Notes |
|---|---|---|
sub | sub | The principal’s stable subject id. |
principal_type | geopera_principal_type | user for a person, or a service-principal type for an API key. |
org_id | geopera_org_id | The organisation the principal belongs to. |
scope | scope | Scopes actually granted on this credential. |
api_url | resolved at call time | The base URL the request went to. |
profile | resolved at call time | The active profile name. |
Add --json to print the raw userinfo payload instead of the formatted summary — useful for scripting:
geopera whoami --json{
"sub": "3f6c1b2a-9a4e-4c0f-8b21-1d2e3f4a5b6c",
"geopera_principal_type": "user",
"geopera_org_id": "org_8a1c...",
"scope": "openid profile"
}whoami accepts --profile and --api-url like the other commands, so you can check a non-active identity without switching:
geopera whoami --profile stagingIf there are no credentials for the active profile, whoami exits non-zero with Not logged in (profile '...'). Run 'geopera login' first.
Token refresh and rotation
OAuth access tokens refresh automatically, so you rarely need to think about it. There are two paths:
- Proactive — before each command, if the stored access token is missing an expiry or is within 30 seconds of expiring, the CLI exchanges the refresh token for a fresh access token first, then runs the command.
- Reactive — if a request returns
401anyway (for example, the token died between the proactive check and the call, or was revoked server-side), the CLI refreshes once and retries the request. If the retry also fails, the error is surfaced.
Both paths use refresh-token rotation: a new refresh token is issued on every use, and the CLI persists both the new access token and the new refresh token atomically. If the response omits a new refresh token, the CLI keeps the previous one as a fallback.
If the refresh token has been revoked or is missing, the CLI reports that your session expired and asks you to run geopera login again:
Error: Token refresh failed (your session may have been revoked). Run 'geopera login' to sign in again.API keys do not refresh — a 401 on an API-key credential is terminal. The CLI does not retry; it reports:
Error: API key rejected (401). Check the key or create a new one.Profiles
Every credential lives under a named profile, letting you keep separate identities (for example production and staging, or a personal login and a service key) side by side. The active profile is resolved with this precedence, highest first:
- the
--profileflag - the
GEOPERA_PROFILEenvironment variable - the built-in default,
default
Each profile also carries its own api_url, resolved independently:
- the
--api-urlflag - the
GEOPERA_API_URLenvironment variable - the
api_urlstored in the active profile - the built-in default,
https://api.geopera.com
Sign two profiles in against different endpoints:
geopera login --profile default
geopera login --profile staging --api-url https://staging.api.geopera.com --api-key -Then target either one per command:
geopera whoami --profile staging
GEOPERA_PROFILE=staging geopera orders listBecause each profile remembers its api_url, a later geopera login --profile staging reuses the staging endpoint without you re-specifying --api-url.
Where credentials are stored
Credentials live in a single JSON file at ~/.config/geopera/credentials.json. The directory is created with mode 0700 and the file is written atomically with mode 0600 (the temp file is opened 0600 from the start), so secrets are never briefly world-readable. Writes are atomic via a temp-file-then-rename, so a crash mid-write cannot corrupt the store or leak a partial secret.
Each top-level key is a profile holding an api_url and an auth block. OAuth and API-key profiles have different auth shapes:
{
"default": {
"api_url": "https://api.geopera.com",
"auth": {
"type": "oauth",
"access_token": "...",
"refresh_token": "...",
"expires_at": 1750000000,
"scope": "openid profile",
"issuer": "https://api.geopera.com"
}
},
"staging": {
"api_url": "https://staging.api.geopera.com",
"auth": { "type": "api_key", "api_key": "gpra_..." }
}
}| Field | Profiles | Meaning |
|---|---|---|
type | both | oauth or api_key; selects the auth header at request time. |
access_token | oauth | The bearer access token. |
refresh_token | oauth | Rotated on every refresh; used to mint new access tokens. |
expires_at | oauth | Unix epoch seconds; drives the proactive 30-second refresh window. |
scope | oauth | Scopes granted at login. |
issuer | oauth | The API base URL the token was issued by. |
api_key | api_key | The gpra_ key, sent verbatim as X-API-Key. |
You do not edit this file by hand in normal use — login and logout manage it for you — but its layout is documented so you can audit or back it up.
Logging out
geopera logout clears the active profile’s stored auth block. For an OAuth session it also makes a best-effort RP-initiated logout call to revoke the session server-side (failures there are ignored, since the local credential is being cleared regardless). The profile’s api_url is kept, so a later geopera login reuses the same endpoint without you re-specifying --api-url.
geopera logoutLogged out (profile 'default').Target a specific profile with --profile:
geopera logout --profile stagingIf there was nothing to clear:
No stored credentials for profile 'default'.Logging out clears only the named profile; other profiles in the store are left untouched. GEOPERA_API_TOKEN is unaffected by logout — it is never stored, so there is nothing to clear.
Worked example: CI pipeline
Authenticate non-interactively, confirm the principal, then run a command — all without touching a browser or writing long-lived state to disk.
#!/usr/bin/env bash
set -euo pipefail
# Inject the key from the CI secret store; never echo it.
echo "$GEOPERA_API_KEY" | geopera login --api-key -
# Confirm we authenticated as the expected service principal.
geopera whoami
# Run any operation as a structured command. Scalar fields are flags;
# complex bodies use --json '<json>' / @file.json / - (stdin).
geopera orders list
geopera catalog search --collections sentinel-2-l2a --limit 10For a fully stateless run, drop the login step and pass the key through the environment instead — nothing is written to ~/.config/geopera:
GEOPERA_API_TOKEN="$GEOPERA_API_KEY" geopera orders listIf you need to drive an operation that is not exposed as a named command — for example one generated dynamically in a script — the low-level escape hatch still works: geopera op <operation_id> '<json>', such as geopera op orders.list '{}'. The body can also come from a file (--file body.json) or stdin (-).
Troubleshooting
| Symptom | Cause and fix |
|---|---|
Not logged in (profile '...'). Run 'geopera login' first. | No stored credentials for the active profile. Run geopera login, or set GEOPERA_API_TOKEN. |
Token refresh failed (your session may have been revoked). | The refresh token was revoked or expired. Run geopera login again. |
Session expired and no refresh token is stored. | The stored OAuth entry has no refresh token. Run geopera login again. |
API key rejected (401). | The API key is invalid or revoked. Check it or mint a new one. |
API key validation failed: ... | The key failed its userinfo check at login and was not saved. Verify the key value. |
No API key provided. | --api-key - got an empty stdin, or --api-key "". Supply a real key. |
Device authorization failed. The server may not support the device flow yet | Fall back to geopera login --api-key <key>. |
Login was denied in the browser. | You rejected the request. Rerun geopera login and approve it. |
Login timed out before authorization completed. | You did not confirm the code in time (~10 minutes). Rerun geopera login. |
Related
- Authentication — the token model: session tokens,
gpra_API keys, and how the API validates them. - Scopes — what a credential is allowed to do.
- Errors — the problem+json error shape returned by operations.
- Operations — the structured commands the CLI exposes, and the
POST /v1/op/{operation_id}model they dispatch to.