Skip to content
getgeolens.com

CLI & Manifests

The GeoLens CLI (geolens) is the fastest way to get data into an instance from a terminal or CI pipeline. It wraps the Python SDK to publish single files, scan directories, export STAC metadata, and apply declarative manifests (geolens.yaml) that describe a whole catalog.

Terminal window
pip install geolens-cli # installs the `geolens` command
# or, for an isolated tool install:
pipx install geolens-cli

Verify the install:

Terminal window
geolens --version
geolens --help

To run a command without installing anything, uvx fetches the package on demand:

Terminal window
uvx --from geolens-cli geolens --version

Pin the version in automation (uvx --from geolens-cli==<version> geolens ...). The examples repo keeps the pinned form of the manifest commands on this page, next to a working manifest and GitHub Actions workflow.

Log in to an instance and store credentials. The instance URL is the API base, so include the /api suffix:

Terminal window
geolens login http://localhost:8080/api
# prompts for your admin username and password (auto-generated at install; see your .env)

By default the bearer token is stored in your operating system keyring. Pass --no-keyring to fall back to a credentials.toml file instead (useful on headless hosts without a keyring service):

Terminal window
geolens login https://geolens.example.com/api --no-keyring

You can also store a token or API key non-interactively (handy for scripts):

Terminal window
geolens login https://geolens.example.com/api --token "$JWT"
geolens login https://geolens.example.com/api --api-key "$GEOLENS_API_KEY"
# pass `-` to read the secret from stdin (keeps it out of argv / shell history):
echo "$JWT" | geolens login https://geolens.example.com/api --token -

--token and --api-key are mutually exclusive; pass only one.

Check who you are, or clear credentials:

Terminal window
geolens whoami # prints the active user and instance
geolens logout # removes stored credentials for the active instance

geolens publish uploads one local vector or raster file and runs the full ingest flow (upload -> preview -> commit), then prints the new dataset’s URL:

Terminal window
geolens publish ./city-parks.geojson --name "City Parks"
geolens publish ./elevation.tif --name "Elevation" --description "10m DEM"

By default the command waits for ingestion to resolve the dataset id. Use --no-wait to return immediately with a job-search URL instead:

Terminal window
geolens publish ./big-raster.tif --no-wait

geolens scan walks a directory and reports what would be ingested, a dry run with no upload. Use it to preview a bulk import:

Terminal window
geolens scan ./gis-exports
geolens scan ./gis-exports --include-ext .gpkg,.tif --max-depth 2
geolens scan ./gis-exports --json # machine-readable output

For multi-dataset catalogs that you want to version and re-apply, describe your sources in a geolens.yaml manifest and apply it declaratively.

Terminal window
geolens init # scaffold ./geolens.yaml (errors if it already exists)
geolens init --force # overwrite an existing manifest
geolens init catalog.yaml # scaffold at a custom path
geolens validate geolens.yaml # local schema check, no API call
geolens apply geolens.yaml # validate + apply via the GeoLens API
geolens apply geolens.yaml --dry-run # preview apply outcomes without writes

Each dataset carries a stable key, so re-applying an edited manifest updates the matching datasets instead of creating duplicates. The server matches each entry to an existing dataset by key and fingerprints the rest, then answers create (new key), update (known key, changed entry), or skip (unchanged entry) per dataset. An unchanged manifest is therefore safe to apply on every push. --dry-run returns the same verdicts without writing, but the server evaluates it, so it needs a credential like a real apply.

Apply reconciles the declaration rather than the bytes behind it: a source URL that serves new content under an unchanged entry still skips. geolens refresh <dataset_id> is the manual refresh from the dataset page, for datasets with an upstream to re-pull (Service, registered PostGIS table, STAC). A source the server downloaded for a manifest is an ordinary upload once ingested, so refresh refuses it. See Where Your Data Lives.

The top level requires manifest_version: "1", a catalog block, and a non-empty datasets array.

Print the packaged JSON Schema for geolens.yaml — for editor validation or CI schema checks — without contacting an API:

Terminal window
geolens schema # print to stdout
geolens schema -o geolens.schema.json # write to a file

catalog: required title; optional description, organization, and a contact object (name, email, url).

datasets[]

| Field | Required | Notes | |---|---|---| | key | Yes | Stable identity for idempotent apply. Must start with a lowercase letter or digit, then a-z 0-9 . _ -; up to 128 chars. | | title | Yes | Human-readable dataset title. | | description | No | Longer description. | | sources | Yes | Exactly one source object (below); the schema caps the array at one entry. | | metadata | No | Tags, CRS, license, bbox (below). | | publication | Yes | Publication intent (below). |

sources[]

| Field | Required | Notes | |---|---|---| | type | Yes | One of vector, raster_cog. | | uri | Yes | Relative path, https://, or s3:// / gs:// / az:// / abfs:// URI. Must end in an extension the ingest path recognizes: zip, gpkg, geojson, json, csv, xlsx, or xls for vector; tif or tiff for raster_cog. A query string or fragment may follow. | | format | No | Driver hint, e.g. geojson, gpkg. | | layer | No | Layer name for multi-layer sources. | | title / description | No | Per-source overrides. |

metadata (all optional): tags (string array), organization, crs (EPSG:NNNN), license, attribution, bbox ([minx, miny, maxx, maxy] in WGS84).

publication: required intent. The schema does not fix the values; they come from the workflow statuses your deployment defines, and the server validates them at apply time. The community default set is draft, ready, internal, published, which is why geolens validate can accept an intent your instance then rejects.

manifest_version: "1"
catalog:
title: Regional Open Data
description: Public datasets published by the Regional Data Office.
organization: Regional Open Data Office
datasets:
- key: regional-trails
title: Regional trails
description: Recreational trail network.
sources:
- type: vector
uri: https://data.example.com/trails.geojson
format: geojson
metadata:
tags: [trails, recreation]
crs: EPSG:4326
license: CC-BY-4.0
attribution: Regional Open Data Office
bbox: [-78.0, 38.0, -76.5, 39.5]
publication:
intent: ready

Export STAC API 1.0 item metadata for a raster dataset (vector datasets are rejected with a clear message):

Terminal window
geolens export stac <dataset_id> # pretty JSON to stdout
geolens export stac <dataset_id> -o item.json # write to a file
geolens export stac <dataset_id> --compact # single line, for piping to jq

The CLI is built for pipelines: authenticate through the environment, validate offline, preview with --dry-run on a pull request, apply on a push to main. --json is a global option and goes before the subcommand (geolens --json apply geolens.yaml; geolens apply --json is an error), and every command returns a non-zero exit code on failure.

The examples repo has a ready-made GitHub Actions workflow that splits validate, preview, and apply into three jobs so the write token never reaches a pull request, and a walkthrough of the secrets and environment it expects.

These apply to every command and go before the subcommand:

| Flag | Effect | |---|---| | --instance <url> | Override the active instance for this command. | | --json | Machine-readable JSON output. | | -v, --verbose | Debug logging to stderr. | | -q, --quiet | Suppress non-error output. | | --version | Print the CLI version and exit. |