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:

CommandWhat it does
geopera loginSign in via the device flow, or store an API key with --api-key.
geopera logoutClear the active profile’s stored credentials (and revoke an OAuth session).
geopera whoamiResolve 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.

bash
geopera login

The flow is:

  1. The CLI generates a PKCE verifier and an S256 challenge, then calls the device-authorization endpoint as a public native client (client id geopera-cli, no secret).
  2. It prints a verification URL and a short user code, and — unless --no-browser is set — opens the URL in your default browser. The CLI prefers the server’s verification_uri_complete (the URL with the code already embedded) and falls back to the bare verification_uri.
  3. 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 replies slow_down, and treating authorization_pending as “keep waiting”.
  4. On success it stores the access and refresh tokens, then confirms with a userinfo round-trip and prints the resolved principal.

Typical output:

bash
  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

OptionDefaultDescription
--no-browseroffDo not auto-open the verification URL. Print the code and URL only.
--scope <scope>openid profileOAuth 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.comAPI base URL override (env: GEOPERA_API_URL).
--profile <name>defaultStored 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:

bash
geopera login --no-browser

Request a different scope set with --scope (space-separated, quoted):

bash
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:

FieldMeaningCLI behaviour
intervalSeconds to wait between pollsSleeps this long between token polls (default 5 if omitted).
expires_inLifetime of the device requestUsed as the polling deadline (default 600, about 10 minutes).
verification_uri_completeURL with the code pre-filledOpened 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 reports Login timed out before authorization completed. Rerun geopera login.
  • Unsupported — if the device-authorization endpoint itself is unavailable, the CLI reports Device authorization failed. The server may not support the device flow yet and suggests geopera 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.

bash
geopera login --api-key gpra_xxxxxxxxxxxxxxxxxxxxxxxx

To keep the key out of your shell history, pass - and feed it on stdin:

bash
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:

bash
Logged in as svc_8a1c... (API key, profile 'default').

If validation fails the key is not saved and the command exits non-zero:

bash
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_:

bash
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 typeHeader 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 as X-API-Key.
  • Any other value is treated as an opaque session token and sent as Authorization: Bearer ....
bash
export GEOPERA_API_TOKEN="gpra_xxxxxxxxxxxxxxxxxxxxxxxx"
geopera whoami

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

bash
geopera whoami
bash
sub:             3f6c1b2a-9a4e-4c0f-8b21-1d2e3f4a5b6c
principal_type:  user
org_id:          org_8a1c...
scope:           openid profile
api_url:         https://api.geopera.com
profile:         default

The fields map directly onto the userinfo payload:

LineSource fieldNotes
subsubThe principal’s stable subject id.
principal_typegeopera_principal_typeuser for a person, or a service-principal type for an API key.
org_idgeopera_org_idThe organisation the principal belongs to.
scopescopeScopes actually granted on this credential.
api_urlresolved at call timeThe base URL the request went to.
profileresolved at call timeThe active profile name.

Add --json to print the raw userinfo payload instead of the formatted summary — useful for scripting:

bash
geopera whoami --json
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:

bash
geopera whoami --profile staging

If 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 401 anyway (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:

bash
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:

bash
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:

  1. the --profile flag
  2. the GEOPERA_PROFILE environment variable
  3. the built-in default, default

Each profile also carries its own api_url, resolved independently:

  1. the --api-url flag
  2. the GEOPERA_API_URL environment variable
  3. the api_url stored in the active profile
  4. the built-in default, https://api.geopera.com

Sign two profiles in against different endpoints:

bash
geopera login --profile default
geopera login --profile staging --api-url https://staging.api.geopera.com --api-key -

Then target either one per command:

bash
geopera whoami --profile staging
GEOPERA_PROFILE=staging geopera orders list

Because 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:

json
{
	"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_..." }
	}
}
FieldProfilesMeaning
typebothoauth or api_key; selects the auth header at request time.
access_tokenoauthThe bearer access token.
refresh_tokenoauthRotated on every refresh; used to mint new access tokens.
expires_atoauthUnix epoch seconds; drives the proactive 30-second refresh window.
scopeoauthScopes granted at login.
issueroauthThe API base URL the token was issued by.
api_keyapi_keyThe 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.

bash
geopera logout
bash
Logged out (profile 'default').

Target a specific profile with --profile:

bash
geopera logout --profile staging

If there was nothing to clear:

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

bash
#!/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 10

For a fully stateless run, drop the login step and pass the key through the environment instead — nothing is written to ~/.config/geopera:

bash
GEOPERA_API_TOKEN="$GEOPERA_API_KEY" geopera orders list

If 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

SymptomCause 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 yetFall 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.