Profiles & configuration
The geopera CLI keeps each identity in a named profile inside a single, mode-hardened credentials file, and resolves the API base URL through a short precedence chain that environment variables and flags can override. This page documents exactly where that state lives, how every setting is resolved, and the edge cases worth knowing before you script against it.
Almost everything here is settled by two functions in the CLI’s config module — resolve_profile and resolve_api_url — plus the credential store in the auth module. The defaults are deliberately boring: install the CLI, run geopera login, and you never touch any of it. The knobs below exist for multi-environment work, CI, and debugging.
The credential store
Credentials live in one JSON file under your XDG config directory:
~/.config/geopera/credentials.jsonThe directory is created with mode 0700 (owner-only) and the file is written with mode 0600, so the session tokens and API keys it holds are never group- or world-readable. The CLI re-chmods both the directory and the file on every write, because os.makedirs(mode=...) only applies to the leaf directory it creates and is still subject to your umask — the explicit chmod guarantees the final permissions regardless.
Writes are atomic. The store is written to a sibling credentials.json.tmp, opened 0600 from the very first byte (via os.open(..., O_CREAT | O_TRUNC, 0o600)) so the secret is never momentarily world-readable, then os.replaced over the real file. A crash mid-write can therefore never leave a truncated or partially-secret credentials.json behind — you either have the old file intact or the new one complete. The temp file is unlinked in a finally block, so a failed write does not litter .tmp files.
The file is written with sort_keys=True and two-space indentation, so profiles appear alphabetically and diffs stay stable. You normally never edit it by hand:
geopera logincreates and updates a profile.geopera logoutclears the active profile’s credentials (see the gotcha aboutapi_url).
If the file is missing, unreadable, or contains malformed JSON, the store loader returns an empty store rather than raising — so a corrupt file degrades to “not logged in” instead of crashing every command. The fix in that case is simply to log in again, which rewrites the file cleanly.
Profiles
Every identity is a top-level key in the store, keyed by profile name. Each profile entry has exactly two fields: api_url and auth.
{
"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_..." }
}
}The auth block is one of two shapes:
auth.type | Fields | Sent as |
|---|---|---|
oauth | access_token, refresh_token, expires_at, scope, issuer | Authorization: Bearer <access_token> |
api_key | api_key | X-API-Key: <api_key> |
A type: oauth profile is what a browser sign-in produces: geopera login runs the device flow and stores the resulting session token (plus its refresh material and expiry). A type: api_key profile comes from geopera login --api-key and stores a long-lived gpra_ key verbatim. The CLI picks the auth header automatically per request — Authorization: Bearer ... for a session token, X-API-Key: ... for an API key. For how these credentials are obtained and how a session token is refreshed near expiry, see Authentication and the CLI authentication page.
You can log in to as many profiles as you like; each lives independently in the store. Saving a profile rewrites only the one profile you touched and leaves the others intact, so logging into staging never disturbs default.
Selecting a profile
Profile selection follows this precedence (highest first):
| Precedence | Source | Notes |
|---|---|---|
| 1 (highest) | --profile <name> flag | Per-command. Wins over everything. |
| 2 | GEOPERA_PROFILE env var | Applies to the whole shell session. |
| 3 (default) | the literal "default" | Used when neither of the above is set. |
# Use the 'staging' profile for a single command
geopera whoami --profile staging
# Make 'staging' the default for this shell session
export GEOPERA_PROFILE=staging
geopera whoami--profile is a shared option accepted by every command that needs credentials (login, logout, whoami, every geopera <resource> <action> command, and the raw op escape hatch). The resolution rule is the same everywhere: flag, then env var, then "default".
Resolving the API base URL
The base URL the CLI talks to is resolved per command with this precedence (highest first):
| Precedence | Source | Notes |
|---|---|---|
| 1 (highest) | --api-url <url> flag | Per-command override. |
| 2 | GEOPERA_API_URL env var | Whole-session override. |
| 3 | the active profile’s stored api_url | Captured at login time. |
| 4 (default) | https://api.geopera.com | The production API. |
The resolved value has any trailing slash stripped, so https://api.geopera.com/ and https://api.geopera.com behave identically and never produce a doubled //v1/... path.
# One-off override against a local backend
geopera catalog search --collections sentinel-2-l2a --limit 10 \
--api-url http://localhost:8000
# Override for the whole session
export GEOPERA_API_URL=https://staging.api.geopera.com
geopera whoamiBecause each profile already stores its own api_url (captured at login time), you rarely need --api-url or GEOPERA_API_URL in day-to-day use — switching profiles already switches the endpoint. The override flags are there for pointing an existing identity at a different deployment without re-logging-in, which is most useful when testing a staging or local backend with production-shaped credentials.
The GEOPERA_API_TOKEN escape hatch
For CI and other ephemeral environments where you do not want to write a credentials file at all, set GEOPERA_API_TOKEN. When it is present it bypasses the credential store entirely — no profile auth lookup, no refresh, nothing written to disk:
export GEOPERA_API_TOKEN="gpra_xxxxxxxxxxxxxxxxxxxxxxxx"
geopera orders listHow the value is interpreted depends on its prefix:
- A value beginning with
gpra_is treated as an API key and sent asX-API-Key. - Any other value is treated as an opaque bearer token (a session token) and sent as
Authorization: Bearer <value>.
The opaque-bearer mode lets you inject, for example, a session token minted by your CI provider without the CLI trying to refresh it. In this mode expires_at is set to null, so no proactive refresh is ever attempted — the token is used exactly as given, and a 401 surfaces as an error rather than triggering a refresh-and-retry. (Refresh-on-401 only ever runs for a stored oauth profile that actually carries a refresh_token.)
--api-url / GEOPERA_API_URL still apply on top of the env-token path, and the active profile’s stored api_url is still consulted as the default. That means even when the token comes from the environment, the endpoint is resolved with the full precedence chain above, so you can point an env-token command at any deployment:
GEOPERA_API_TOKEN="$CI_GEOPERA_TOKEN" \
geopera orders list --api-url https://staging.api.geopera.comNote the asymmetry: GEOPERA_API_TOKEN short-circuits the auth lookup but does not short-circuit URL resolution. The profile is still resolved (so the profile name shown by whoami reflects --profile/GEOPERA_PROFILE), and its stored api_url is still used as the level-3 default — only the profile’s auth block is ignored in favour of the env token.
Environment variables
| Variable | Purpose | Precedence note |
|---|---|---|
GEOPERA_PROFILE | Active profile name | Below --profile, above the "default" fallback |
GEOPERA_API_URL | API base URL | Below --api-url, above the profile’s stored api_url |
GEOPERA_API_TOKEN | Opaque bearer / API key; bypasses the credential store’s auth | Overrides the entire stored auth; URL resolution is unaffected |
There is no env var for the credential file location — the path is fixed at ~/.config/geopera/credentials.json. If you need an isolated store (for example to keep CI credentials out of your interactive profile), prefer the GEOPERA_API_TOKEN escape hatch over relocating the file.
Worked example: a dedicated CI profile
Suppose you maintain a long-lived default profile for interactive work and want CI to run against staging with a scoped API key, without ever writing to the runner’s home directory.
Locally, mint and store the staging key under its own profile so you can test the exact path CI will use:
geopera login --api-key gpra_xxxxxxxxxxxxxxxxxxxxxxxx \
--api-url https://staging.api.geopera.com \
--profile staging
geopera whoami --profile stagingsub: [email protected]
principal_type: service_account
org_id: org_01H...
scope: orders:read orders:write
api_url: https://staging.api.geopera.com
profile: stagingIn CI, do not log in at all — hand the key in through the environment so nothing is persisted on the runner:
# .github/workflows/smoke.yml (excerpt)
env:
GEOPERA_API_TOKEN: ${{ secrets.GEOPERA_STAGING_KEY }}
GEOPERA_API_URL: https://staging.api.geopera.com
steps:
- run: geopera orders listBecause GEOPERA_API_TOKEN starts with gpra_, the CLI sends it as an API key and the credential store is never read or written. If you would rather pin the endpoint per command than via GEOPERA_API_URL, drop the env var and add --api-url https://staging.api.geopera.com to each invocation — the precedence is identical, the flag simply wins one level higher.
Inspecting the resolved configuration
geopera whoami is the quickest way to confirm which identity, endpoint, and profile a command will actually use — it prints the resolved api_url and profile alongside the principal:
geopera whoami --profile staging --api-url https://staging.api.geopera.comsub: [email protected]
principal_type: service_account
org_id: org_01H...
scope: orders:read orders:write
api_url: https://staging.api.geopera.com
profile: stagingFor the machine-readable form, add --json to get the raw userinfo payload (see the gotcha about what --json is bound to internally):
geopera whoami --jsonGotchas
- There is no
geopera --version. The CLI does not register a--versionflag. To check what you have installed, query the package directly — for examplepip show geopera— since the CLI is a thin shell over the publishedgeoperaSDK. logoutkeepsapi_url.geopera logoutremoves only theauthblock from the active profile; the profile’s storedapi_urlis preserved. A subsequentgeopera loginagainst that profile therefore defaults back to the same endpoint. Iflogoutfinds no storedauth, it reportsNo stored credentials for profile '<name>'.and changes nothing.--jsonis internally therawparameter. Commands such aswhoamiaccept a--jsonflag that switches output from the human-readable summary to raw JSON. In the CLI source this option is bound to a parameter namedraw, so any error message or shell-completion entry referring torawis talking about--json. (This is unrelated to the structured commands’--jsonbody flag, which carries an operation’s request payload.)GEOPERA_API_TOKENoverrides auth but not the URL. Setting it ignores the storedauthfor the resolved profile, but the profile is still resolved and itsapi_urlis still used as the level-3 default. SetGEOPERA_API_URLor--api-urlif you also need to change the endpoint.- A non-
gpra_API key warns but still works.geopera login --api-keyprints a note to stderr if the key does not start withgpra_, then validates and stores it anyway. New Geopera keys are expected to use thegpra_prefix. - A corrupt store degrades gracefully. If
credentials.jsonis unreadable or contains invalid JSON, the loader treats it as empty rather than erroring — every command then behaves as “not logged in” until yougeopera loginagain, which rewrites the file. - Trailing slashes are stripped. Whatever endpoint you supply via
--api-url,GEOPERA_API_URL, or a storedapi_url, the resolved value has its trailing/removed, sohttps://api.geopera.com/andhttps://api.geopera.comare equivalent.
See also
- CLI authentication —
login,logout,whoami, the device flow, and API keys - Authentication — session tokens,
gpra_API keys, and how the API validates them - Scopes — what a credential is allowed to do
- Operations — the
POST /v1/op/{operation_id}model the CLI dispatches to - Errors — the problem+json shape the CLI surfaces on failure