Python SDK¶
The official Python client is geojibe. It is an HTTPS client for an existing GeoJibe installation. Processing stays on the server. It does not run Docker, GDAL, DuckDB, QGIS, or a GeoJibe server on your computer.
The CLI (geojibe) is a thin wrapper around the same GeoJibe object. Use either from the same package.
This page is the remote SDK. Canvas Python scripts that run inside a workflow are documented under Use Python for custom changes.
Install¶
or
Python 3.10 or newer.
Authenticate¶
Create a token in GeoJibe → Account → API Tokens, then:
url is the installation origin (no trailing slash, not /api/v1). Do not hard-code a hosted GeoJibe URL — self-hosted deployments use their own host.
url and token may also come from GEOJIBE_URL and GEOJIBE_TOKEN when omitted. Explicit constructor arguments always win. The token is sent as Authorization: Bearer and is never logged.
Credential precedence, highest wins:
- Constructor
url=/token= GEOJIBE_URL/GEOJIBE_TOKEN- Saved CLI configuration (
geojibe auth login)
Jobs vs Workspaces¶
| Resource | Meaning |
|---|---|
| Jobs | Persistent automation. Can be scheduled and run repeatedly. |
| Workspaces | Temporary interactive processing. Upload → preview → transform → convert → download. Automatically expire. |
client.jobs is saved Jobs. client.workspaces is ephemeral processing. They are not interchangeable.
GeoJibe¶
from geojibe import GeoJibe, ValidationError
with GeoJibe(url="https://<host>", token="gjp_...") as client:
profile = client.me()
| Attribute | Resource |
|---|---|
client.sources |
Saved Sources |
client.destinations |
Saved Destinations |
client.jobs |
Saved Jobs and Runs |
client.recipes |
Saved Recipes |
client.workspaces |
Temporary Workspaces |
client.transformations |
Server transformation catalog |
timeout (default 30s) is the ordinary HTTP timeout. run_timeout (default 600s) is used for upload, preview, convert, and download.
Sources¶
sources = client.sources.list()
src = client.sources.get(sources[0]["id"])
created = client.sources.create({
"name": "Public neighborhoods",
"type": "url",
"configuration": {"url": "https://example.com/neighborhoods.geojson"},
})
client.sources.update(created["id"], {"name": "Neighborhoods URL", "type": "url", "configuration": {"url": "https://example.com/neighborhoods.geojson"}})
client.sources.delete(created["id"])
create sends POST /api/v1/sources. type and configuration match the public Source contract. Secrets in GET responses are omitted; use flags such as has_password.
Destinations¶
destinations = client.destinations.list()
dest = client.destinations.get(destinations[0]["id"])
created = client.destinations.create({
"name": "Production PostGIS",
"type": "postgis",
"configuration": {
"host": "db.example.com",
"database": "gis",
"username": "etl",
"password": "...",
"ssl_mode": "prefer",
},
})
client.destinations.delete(created["id"])
create sends POST /api/v1/destinations. Per-run targets (schema, table, object key) belong on the Job, not the saved Destination.
Jobs¶
jobs = client.jobs.list()
job = client.jobs.get(jobs[0]["id"])
run = client.jobs.run(job["id"])
runs = client.jobs.list_runs(job["id"])
run(..., wait=True) polls until the Run finishes if the first response is still queued or running. The CLI equivalent is geojibe jobs run <id> --wait.
transform_mode is manual, recipe, or sql (not basic). Use recipe_id with recipe.
from geojibe import PipelineStages, job_pipeline, job_write_body, pipeline_stages, stage_included
created = client.jobs.create({
"name": "Neighborhoods",
"source_id": "...",
"transform_mode": "sql",
"sql": "SELECT * FROM source WHERE population > 1000",
"output_format": "GeoJSON",
"destination_id": "...",
"destination_target": {
"schema": "public",
"table": "neighborhoods",
"write_mode": "replace",
},
"schedule": {"kind": "daily", "timezone": "UTC", "hour": 2, "minute": 0},
})
client.jobs.enable(created["id"])
client.jobs.disable(created["id"])
client.jobs.delete(created["id"])
create and update send the public Job body (POST /api/v1/jobs,
PUT /api/v1/jobs/{id}). Existing JSON without pipeline is unchanged and
keeps legacy/inferred stage presence.
Optional Canvas stage flags:
created = client.jobs.create({
"name": "Neighborhoods",
"source_id": "...",
"output_format": "GeoJSON",
"pipeline": pipeline_stages(transform=True, convert=True, python=False),
})
job_pipeline(created)
stage_included(created, "python")
job_write_body({"name": "Neighborhoods", "pipeline": {"transform": False}})
pipeline |
Meaning |
|---|---|
| omitted | Legacy/inferred behavior (stages stay present) |
{} |
Legacy/inferred behavior (stages stay present) |
explicit false |
That stage is absent |
explicit true |
That stage is present |
Create and update preserve explicit false flags. Leftover operations,
format, or Python on the body do not restore a stage marked false.
Recipes¶
recipes = client.recipes.list()
recipe = client.recipes.get(recipes[0]["id"])
created = client.recipes.create({
"name": "Rename shape_len",
"operations": [
{"type": "rename_column", "from": "shape_len", "to": "perimeter"}
],
})
client.recipes.delete(created["id"])
A Recipe stores Transform operations. It is not a Job.
Transformations¶
Discover operations from the server. Do not hard-code a catalog.
Stable identifiers are catalog id values such as buffer, simplify, and rename_column. SQL and Python are sibling fields on a Workspace pipeline, not fake operation type values. See Transform operations.
Workspaces¶
A Workspace is temporary interactive processing. Typical flow:
from geojibe import GeoJibe, ValidationError
client = GeoJibe(url="https://<host>", token="gjp_...")
ws = client.workspaces.upload("bees.gpkg")
print(ws.id, ws.status, ws.layers)
rows = ws.preview_data("apiary", limit=50)
geojson = ws.preview_map("apiary")["geojson"]
ops = client.transformations.list()
ws.set_transformations(
layers=["apiary"],
operations=[{"type": "buffer", "distance": 10, "units": "source"}],
)
ws.preview_transformed_data("apiary", limit=50)
ws.preview_transformed_map("apiary")
ws.convert(output_format="GeoJSON", layers=["apiary"])
output = ws.download("buffered-bees.geojson")
ws.clear_transformations()
SQL and Python are sibling fields, not operation types:
ws.set_transformations(layers=["apiary"], sql="SELECT * FROM source")
ws.set_transformations(python={"script": script, "filename": "script.py"})
Download streams to disk. An existing file is not overwritten unless you pass overwrite=True. Convert is currently synchronous (status=completed).
preview_data and preview_map return the server JSON, including fields, counts, truncation, and CRS. preview_map()["geojson"] is a GeoJSON FeatureCollection. Visualization is up to the caller.
Preview responses are truncated samples. Convert and download are the full result.
Errors¶
try:
ws.preview_transformed_map("other_layer")
except ValidationError as exc:
print(exc.status_code, exc.code, exc.message, exc.details)
| Exception | Typical cause |
|---|---|
AuthenticationError |
HTTP 401 / missing credentials |
AuthorizationError |
HTTP 403 / missing PAT scope |
NotFoundError |
HTTP 404 |
ValidationError |
HTTP 400 / 409 / 422 |
NetworkError |
DNS, connection refused, TLS, timeout |
ServerError |
HTTP 5xx |
WaitTimeoutError |
Timed out waiting for a Job Run |
ConfigError |
Missing URL, token, or local file |