Public API¶
GeoJibe's public REST API is the contract for the CLI and Python client.
Browser, CLI, and SDK clients all use the same /api/v1 handlers, ownership
rules, and business logic.
The official CLI is geojibe:
See Command-line client and Python SDK.
Self-hosted installations (Dedicated and On-Premise) use this same API against the local user database. There is no central GeoJibe authentication service.
Environment variables¶
Reserve these names for the CLI and SDK:
GEOJIBE_URL is the installation origin (no trailing slash). Do not hard-code
a hosted GeoJibe URL — self-hosted deployments use their own host.
GEOJIBE_CONFIG_DIR optionally overrides the CLI/SDK config directory (the
folder that contains config.toml). Normal installs do not set it.
Never put a token in a query string or URL path.
Authentication¶
Browser sessions continue to use the enciva_session cookie.
External clients authenticate with a Personal Access Token:
Tokens are created in Account → API Tokens. The full gjp_… value is shown
once at creation. GeoJibe stores only a SHA-256 hash.
Session cookies are not accepted as Bearer credentials. A PAT cannot create or revoke other PATs; token management requires a browser session.
Expired and revoked tokens fail with HTTP 401. last_used_at is updated at most
once per minute after a successful PAT authentication.
Authorization¶
A PAT belongs to one user and cannot see another user's Sources, Destinations, Jobs, Recipes, or Workspaces. Scopes only reduce that user's capabilities.
| Scope | Access |
|---|---|
account:read |
Read the token owner's profile (GET /api/v1/auth/me) |
sources:read |
List and get Sources, discover PostGIS schemas/tables, and list or inspect QFieldCloud/Mergin Maps projects |
sources:write |
Create, update, delete, and test Sources |
destinations:read |
List and get Destinations, and discover PostGIS schemas/tables |
destinations:write |
Create, update, delete, and test Destinations |
jobs:read |
List Jobs, get a Job, list Runs |
jobs:write |
Create, update, enable, disable, and delete Jobs |
jobs:run |
Run Now and retry a failed Run. Does not include jobs:read. |
workspaces:read |
Get a Workspace, read its transformation state, preview source or transformed data/map, download its result, and read the transformation catalog. Does not include jobs:read. |
workspaces:write |
Create a Workspace by uploading a dataset or importing a saved Source, and replace its transformation pipeline. Importing a saved Source also requires sources:read. Does not include jobs:write. |
workspaces:run |
Convert a Workspace, or deliver its converted result to a saved Destination. Delivery also requires destinations:read. Does not include workspaces:read or jobs:run. |
recipes:read |
List and get Recipes |
recipes:write |
Create, update, and delete Recipes |
There is no tokens:write scope in V1. Existing tokens keep only the scopes
they were created with. Workspace scopes are not granted to tokens created
before those scopes existed.
Jobs vs Workspaces¶
Jobs and Workspaces are different public resources.
| Resource | Meaning |
|---|---|
| Jobs | Persistent automation. Can be scheduled and run repeatedly. |
| Workspaces | Temporary interactive processing. Used for upload or saved-Source import → inspect → preview → convert → download or saved-Destination delivery. Automatically expire. |
The browser Canvas still uses compatibility routes such as POST /api/v1/inspect,
GET /api/v1/jobs/{id}/data-preview, and POST /api/v1/jobs/{id}/spatial-preview.
Those paths are not the public external contract. External clients should use
/api/v1/workspaces.
Jobs vs pipelines¶
Saved automation Jobs are the public persistent resource. The browser UI still
calls /api/v1/pipelines/… internally; those routes remain as compatibility
aliases.
Public clients should use:
| Action | Method | Path | Scope |
|---|---|---|---|
| List Jobs | GET |
/api/v1/jobs |
jobs:read |
| Get Job | GET |
/api/v1/jobs/{id} |
jobs:read |
| Create Job | POST |
/api/v1/jobs |
jobs:write |
| Update Job | PUT |
/api/v1/jobs/{id} |
jobs:write |
| Delete Job | DELETE |
/api/v1/jobs/{id} |
jobs:write |
| Enable Job | POST |
/api/v1/jobs/{id}/enable |
jobs:write |
| Disable Job | POST |
/api/v1/jobs/{id}/disable |
jobs:write |
| Run Job | POST |
/api/v1/jobs/{id}/run |
jobs:run |
| List Runs | GET |
/api/v1/jobs/{id}/runs |
jobs:read |
| Get Run | GET |
/api/v1/jobs/{id}/runs/{runId} |
jobs:read |
| Retry Run | POST |
/api/v1/jobs/{id}/runs/{runId}/retry |
jobs:run |
| Reset Sync | POST |
/api/v1/jobs/{id}/sync/reset |
jobs:write |
| Reinitialize Sync | POST |
/api/v1/jobs/{id}/sync/reinitialize |
jobs:write |
GET /api/v1/jobs/{id} returns a saved Job when the id matches one the caller
owns. Canvas inspect/preview routes under /api/v1/jobs/{id}/… that are not in
this table are not the public Job contract.
Create and update send JSON such as:
{
"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}
}
transform_mode is manual, recipe, or sql.
| Mode | What you send |
|---|---|
manual |
operations (and optional output_fields) |
recipe |
recipe_id — GeoJibe snapshots the Recipe operations onto the Job |
sql |
sql — one SELECT against source |
SQL is a sibling field (sql), not an operation type. Python on a Job uses
python_script and python_filename. Recipe edits after save do not change
Jobs that already snapshotted that Recipe.
Account¶
| Action | Method | Path | Scope |
|---|---|---|---|
| Current user | GET |
/api/v1/auth/me |
account:read |
Token create, list, and revoke (/api/v1/account/tokens) require a browser
session. A PAT cannot create or revoke other PATs.
Sources¶
| Action | Method | Path | Scope |
|---|---|---|---|
| List Sources | GET |
/api/v1/sources |
sources:read |
| Get Source | GET |
/api/v1/sources/{id} |
sources:read |
| Create Source | POST |
/api/v1/sources |
sources:write |
| Update Source | PUT |
/api/v1/sources/{id} |
sources:write |
| Delete Source | DELETE |
/api/v1/sources/{id} |
sources:write |
| Test draft | POST |
/api/v1/sources/test |
sources:write |
| Test saved | POST |
/api/v1/sources/{id}/test |
sources:write |
Create body:
{
"name": "Public neighborhoods",
"type": "url",
"configuration": {"url": "https://example.com/neighborhoods.geojson"}
}
type is the Source type (url, http, s3, azureblob, gdrive,
featureserver, postgis, sqlserver, mysql, oracle, bigquery,
snowflake, geoserver, geonode, ogcfeatures, stac, ckan, hub,
qfieldcloud, merginmaps). Configuration fields match the saved connection
for that type. Secrets in GET/list responses are omitted.
Saved PostGIS Sources expose schema and table discovery. Omit schema to list
schemas. Pass schema to list tables and views in that schema. Credentials stay
on the server and are never returned.
| Action | Method | Path | Scope |
|---|---|---|---|
| Discover PostGIS | GET |
/api/v1/sources/{id}/discover |
sources:read |
Optional query: schema.
Response:
{
"host": "db.example.com",
"database": "gis",
"schemas": ["public", "field"],
"schema": "public",
"tables": [
{
"schema": "public",
"name": "neighborhoods",
"has_geometry": true,
"geometry_type": "Polygon"
}
]
}
tables is omitted until schema is set. Typical errors: postgis_failed
(connection, authentication, TLS, missing database, permission, timeout),
invalid_source (not a PostGIS Source), source_not_found, forbidden.
QFieldCloud and Mergin Maps Sources can list and inspect projects. These are read operations:
| Action | Method | Path | Scope |
|---|---|---|---|
| List projects | POST |
/api/v1/sources/{id}/projects |
sources:read |
| Inspect project | POST |
/api/v1/sources/{id}/inspect-project |
sources:read |
Destinations¶
| Action | Method | Path | Scope |
|---|---|---|---|
| List Destinations | GET |
/api/v1/destinations |
destinations:read |
| Get Destination | GET |
/api/v1/destinations/{id} |
destinations:read |
| Create Destination | POST |
/api/v1/destinations |
destinations:write |
| Update Destination | PUT |
/api/v1/destinations/{id} |
destinations:write |
| Delete Destination | DELETE |
/api/v1/destinations/{id} |
destinations:write |
| Test draft | POST |
/api/v1/destinations/test |
destinations:write |
| Test saved | POST |
/api/v1/destinations/{id}/test |
destinations:write |
| Discover PostGIS | GET |
/api/v1/destinations/{id}/discover |
destinations:read |
Destination discovery uses the same engine and response shape as Source
discovery. Omit schema to list schemas. Pass schema to list tables. Typical
errors: postgis_failed, invalid_destination, destination_not_found,
forbidden. There is no public draft-connection discover endpoint. Test a
draft, save it, then discover.
Create body:
{
"name": "Production PostGIS",
"type": "postgis",
"configuration": {
"host": "db.example.com",
"database": "gis",
"username": "etl",
"password": "…",
"ssl_mode": "prefer"
}
}
type is the Destination type (postgis, sqlserver, mysql, oracle,
bigquery, snowflake, s3, azureblob, gdrive, sftp, hub,
geoserver, geonode, webhook). Per-run targets (schema, table, object key)
belong on the Job destination_target, not the saved Destination.
Hub browse helpers on /api/v1/sources/{id}/hub/… and
/api/v1/destinations/{id}/hub/… (and the GeoServer/GeoNode listing helpers)
are PAT-accessible with the matching Source or Destination scope. They are
connection helpers, not Canvas inspect routes.
Recipes¶
| Action | Method | Path | Scope |
|---|---|---|---|
| List Recipes | GET |
/api/v1/recipes |
recipes:read |
| Get Recipe | GET |
/api/v1/recipes/{id} |
recipes:read |
| Create Recipe | POST |
/api/v1/recipes |
recipes:write |
| Update Recipe | PUT |
/api/v1/recipes/{id} |
recipes:write |
| Delete Recipe | DELETE |
/api/v1/recipes/{id} |
recipes:write |
Create body:
{
"name": "Rename shape_len",
"operations": [
{"type": "rename_column", "from": "shape_len", "to": "perimeter"}
]
}
A Recipe stores Transform operations. It is not a Job.
Workspaces¶
A Workspace is a temporary processing context. Create one by uploading a local
file or importing a saved Source you own. GeoJibe inspects it, you preview a
layer, convert it, then download the result or deliver it to a saved
Destination. The Workspace expires automatically (default 60 minutes;
ENCIVA_JOB_TTL). Expired workspaces return job_expired. Files are removed
by the existing cleanup. A saved Source does not make the Workspace persistent.
Typical PAT flow:
POST /api/v1/workspaces— upload a file, or import a saved SourceGET /api/v1/workspaces/{id}— read layers and metadataGET /api/v1/workspaces/{id}/data-preview?layer=…— sample source rowsGET /api/v1/workspaces/{id}/spatial-preview?layer=…— source vector GeoJSONGET /api/v1/transformations— transformation catalogPUT /api/v1/workspaces/{id}/transformations— set the ordered pipelineGET /api/v1/workspaces/{id}/transform-preview?layer=…— transformed rowsGET /api/v1/workspaces/{id}/transform-spatial-preview?layer=…— transformed GeoJSONPOST /api/v1/workspaces/{id}/convert— convert using the stored pipelineGET /api/v1/workspaces/{id}/download— download the converted file orPOST /api/v1/workspaces/{id}/deliver— publish it to a saved Destination
Download and delivery are independent. Convert first. Deliver does not convert
with guessed defaults. If no converted output exists, delivery returns
job_not_ready.
Preview is not a substitute for convert/download. Preview responses are truncated samples.
Uploads use the same size limit as Canvas: ENCIVA_MAX_UPLOAD_MB, default
256 MiB. Oversized uploads return upload_too_large with max_bytes in
details.
Shapefiles must be uploaded as a ZIP of the companion files (.shp, .shx,
.dbf, and .prj when available). Native multi-file shapefile upload is not
part of this API.
Workspace IDs do not grant access. Another user's Workspace returns
job_not_found (HTTP 404), the same as a missing id.
| Action | Method | Path | Scope |
|---|---|---|---|
| Transformation catalog | GET |
/api/v1/transformations |
workspaces:read |
| Create Workspace | POST |
/api/v1/workspaces |
workspaces:write (plus sources:read when importing a saved Source) |
| Get Workspace | GET |
/api/v1/workspaces/{id} |
workspaces:read |
| Data Preview | GET |
/api/v1/workspaces/{id}/data-preview |
workspaces:read |
| Spatial Preview | GET |
/api/v1/workspaces/{id}/spatial-preview |
workspaces:read |
| Get transformations | GET |
/api/v1/workspaces/{id}/transformations |
workspaces:read |
| Set transformations | PUT |
/api/v1/workspaces/{id}/transformations |
workspaces:write |
| Transformed Data Preview | GET |
/api/v1/workspaces/{id}/transform-preview |
workspaces:read |
| Transformed Spatial Preview | GET |
/api/v1/workspaces/{id}/transform-spatial-preview |
workspaces:read |
| Convert Workspace | POST |
/api/v1/workspaces/{id}/convert |
workspaces:run |
| Deliver result | POST |
/api/v1/workspaces/{id}/deliver |
workspaces:run + destinations:read |
| Download result | GET |
/api/v1/workspaces/{id}/download |
workspaces:read |
POST /api/v1/workspaces accepts either:
multipart/form-datawith fieldfile— upload and inspect a local datasetapplication/jsonwithsource_idand optionalsource_resource— import a saved Source you own
source_resource is the same per-use selector used by saved Jobs. URL and HTTP
Sources do not need it. Types that select a table, object, layer, collection,
or asset require it and return invalid_parameter or invalid_source when it
is missing.
Supported import types: url, http, s3, azureblob, featureserver,
postgis, sqlserver, mysql, oracle, bigquery, snowflake,
geoserver, geonode, ogcfeatures, stac, hub.
gdrive, ckan, qfieldcloud, merginmaps, and duckdb are not available
on this route.
The server loads and decrypts saved Source credentials. Workspace JSON, errors, and logs never include passwords, access keys, tokens, or private keys.
POST /api/v1/workspaces/{id}/deliver publishes the current converted output
to a saved Destination:
{
"destination_id": "…",
"destination_target": {
"remote_path": "/exports/neighborhoods.geojson",
"write_mode": "replace"
}
}
destination_target is the same per-delivery target used by saved Jobs
(schema/table, object key, remote path, write mode, GeoServer layer, and so
on). A successful response includes public destination and delivery
metadata. Secrets are never returned.
Supported delivery types: postgis, sqlserver, mysql, oracle,
bigquery, snowflake, s3, azureblob, sftp, hub, geoserver,
geonode, webhook.
gdrive and duckdb are not available on this route. Download remains
GET /api/v1/workspaces/{id}/download and is not a saved Destination.
Another user's Source, Destination, or Workspace id returns the existing
non-enumerating 404 (source_not_found, destination_not_found, or
job_not_found).
The JSON representation reuses the existing inspect/convert fields (status,
filename, size, dataset type, format, driver, layers, fields, raster metadata,
CRS, warnings, output, and errors). It also includes expires_at. Filesystem
paths are never returned.
Data Preview¶
GET /api/v1/workspaces/{id}/data-preview returns a bounded attribute sample
for one layer. Geometry is not required — non-spatial tables are valid targets.
Query parameters:
| Parameter | Required | Notes |
|---|---|---|
layer |
Multi-layer workspaces | Omit only when the dataset has a single layer. |
limit |
No | Default 100. Hard maximum 500. Non-positive or non-numeric values return invalid_parameter. |
Example:
{
"workspace_id": "abc123",
"layer": "apiary",
"fields": [
{"name": "nbr_of_boxes", "type": "Integer"},
{"name": "bee_species", "type": "String"}
],
"has_geometry": true,
"geometry_type": "Point",
"crs": "EPSG:3857",
"row_count": 35,
"preview_row_count": 35,
"limit": 100,
"truncated": false,
"rows": [
{"nbr_of_boxes": 4, "bee_species": "apis"}
]
}
preview_row_count is how many rows were returned. row_count is the layer
feature/row count when known. truncated is true when the sample is shorter
than the full table or hit the requested limit. Geometry cells are type labels
(for example POINT), not WKT/WKB coordinates.
Spatial Preview¶
GET /api/v1/workspaces/{id}/spatial-preview returns vector features for map
display. The selected layer must have geometry. A non-spatial table returns
invalid_parameter (HTTP 400) explaining that the layer cannot be spatially
previewed. The response is not empty GeoJSON.
Query parameters:
| Parameter | Required | Notes |
|---|---|---|
layer |
Multi-layer workspaces | Omit only when the dataset has a single layer. |
The existing preview engine reprojects a read-only sample to EPSG:4326 for
standard web maps. The source dataset is not rewritten. source_crs is the
layer CRS; preview_crs is always EPSG:4326. Coordinates in geojson are
WGS 84 lon/lat.
GeoJSON is produced with GDAL RFC7946=YES, so Z/M dimensions (for example
LineStringZM) are flattened to 2D (LineString). Feature properties are
kept for identify/popup use. Filesystem paths and credentials are never
included.
The server caps spatial preview at 2000 features. returned_count,
feature_count, and truncated say whether the response is a sample.
Example:
{
"workspace_id": "abc123",
"layer": "apiary",
"source_crs": "EPSG:3857",
"preview_crs": "EPSG:4326",
"returned_count": 35,
"feature_count": 35,
"truncated": false,
"geojson": {
"type": "FeatureCollection",
"features": []
}
}
Raster preview, QGIS WMS, and private Canvas job routes such as
/api/v1/jobs/{id}/transform-preview are not part of this public Workspace
contract.
Transformations¶
GET /api/v1/transformations is a server-driven capability catalog. Use the
stable id values (for example simplify), not display name labels, as API
identifiers. The catalog describes GeoJibe's existing operations — it does not
add new ones.
PUT /api/v1/workspaces/{id}/transformations replaces the complete ordered
pipeline. The JSON uses the same operation objects as Recipes and Jobs
(type, column, from, to, tolerance, distance, and so on). Order is
significant and is not reordered for vector operations.
{
"layers": ["apiary"],
"operations": [
{"type": "rename_column", "from": "name", "to": "site"},
{"type": "buffer", "distance": 25, "units": "source"}
]
}
{"operations": []} clears the pipeline. The uploaded source file is never
overwritten.
Layer targeting. Operations do not carry a per-operation layer field.
layers on the pipeline selects which layer(s) the operations apply to. A
multi-layer dataset (for example bees.gpkg) requires layers when operations
or SQL are set. A pipeline for apiary does not modify area. Geometry
operations on a non-spatial table fail validation with
invalid_transformation. Attribute and SQL operations may target non-spatial
tables.
SQL. DuckDB SQL is a sibling field, not an operation type. It is
mutually exclusive with operations. Queries must be a single SELECT
against the source relation and use the existing SQL sandbox (no network,
no filesystem reads outside the workspace, no credentials).
Python. When stored, the script runs in the same isolated runtime as
Canvas preprocess (python3 script.py <input> <output>, no network). It is
applied to a temporary copy at preview/convert time. The original upload is
not replaced.
Convert uses the currently stored pipeline. Every convert starts from the
immutable upload plus the current definition — previous outputs are not
stacked. output_format and target_crs remain convert-request fields.
Transformed Data Preview and Transformed Spatial Preview use the stored
pipeline. Query parameters match source preview (layer, and limit for
data). Spatial preview still reprojects a sample to EPSG:4326 for display
only. Preview responses remain truncated samples; convert/download is the
full result.
Workspace transformation state expires with the Workspace (ENCIVA_JOB_TTL).
Convert accepts JSON such as:
output_format values come from GET /api/v1/formats. target_crs is optional.
Download returns Content-Disposition, Content-Length when known, and
Content-Type: application/octet-stream.
The Workspace API requires a browser session or a PAT. It does not accept anonymous access. Failed requests return the standard JSON error envelope, not an HTML login page.
Formats¶
GET /api/v1/formats returns the server-driven GDAL format catalog (vector and
raster input/output). It is public and does not require a PAT. Call it before
presenting output-format choices. Do not hard-code format tables.
System¶
GET /api/v1/system returns non-sensitive runtime capabilities: GDAL version,
driver counts, feature flags (for example raster processing), and the
deployment edition. It is public. Treat container_image as an implementation
detail; do not depend on it.
Secrets¶
GET and list responses for Sources and Destinations never include passwords,
secret keys, private keys, API tokens, or credential JSON. Use flags such as
has_password and has_secret_key.
Errors¶
Failed requests return:
Typical status codes: 200, 201, 204, 400, 401, 403, 404, 409,
422, 500.
PostGIS discover failures use postgis_failed with a message for connection,
authentication, TLS, missing database, missing schema/table, permission, or
timeout. Responses never include passwords, PATs, or connection secrets.
Examples¶
Replace <host> with your installation. Do not paste real tokens into docs or
shell history when you can avoid it.
Current user¶
List Sources¶
List Destinations¶
List Jobs¶
Get Job¶
Run Job¶
curl \
-X POST \
-H "Authorization: Bearer $GEOJIBE_TOKEN" \
"$GEOJIBE_URL/api/v1/jobs/<job-id>/run"
List Recipes¶
List formats¶
Create a Workspace¶
curl \
-H "Authorization: Bearer $GEOJIBE_TOKEN" \
-F "file=@example.gpkg" \
"$GEOJIBE_URL/api/v1/workspaces"
Shapefiles must be a ZIP:
curl \
-H "Authorization: Bearer $GEOJIBE_TOKEN" \
-F "file=@parcels.zip" \
"$GEOJIBE_URL/api/v1/workspaces"
Create a Workspace from a saved Source¶
curl \
-X POST \
-H "Authorization: Bearer $GEOJIBE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"source_id":"<source-id>"}' \
"$GEOJIBE_URL/api/v1/workspaces"
Requires workspaces:write and sources:read. Add source_resource when the
saved Source type needs a table, object, layer, collection, or asset.
Get a Workspace¶
Convert a Workspace¶
curl \
-X POST \
-H "Authorization: Bearer $GEOJIBE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"output_format":"GeoJSON"}' \
"$GEOJIBE_URL/api/v1/workspaces/<workspace-id>/convert"
Preview Workspace rows¶
curl \
-H "Authorization: Bearer $GEOJIBE_TOKEN" \
"$GEOJIBE_URL/api/v1/workspaces/<workspace-id>/data-preview?layer=apiary"
Non-spatial tables use the same endpoint:
curl \
-H "Authorization: Bearer $GEOJIBE_TOKEN" \
"$GEOJIBE_URL/api/v1/workspaces/<workspace-id>/data-preview?layer=Reviews&limit=50"
Preview Workspace features on a map¶
curl \
-H "Authorization: Bearer $GEOJIBE_TOKEN" \
"$GEOJIBE_URL/api/v1/workspaces/<workspace-id>/spatial-preview?layer=apiary"
List available transformations¶
Set Workspace transformations¶
curl \
-X PUT \
-H "Authorization: Bearer $GEOJIBE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"layers":["apiary"],"operations":[{"type":"buffer","distance":25,"units":"source"}]}' \
"$GEOJIBE_URL/api/v1/workspaces/<workspace-id>/transformations"
Read the stored pipeline:
curl \
-H "Authorization: Bearer $GEOJIBE_TOKEN" \
"$GEOJIBE_URL/api/v1/workspaces/<workspace-id>/transformations"
Clear it with an empty replacement:
curl \
-X PUT \
-H "Authorization: Bearer $GEOJIBE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"operations":[]}' \
"$GEOJIBE_URL/api/v1/workspaces/<workspace-id>/transformations"
Preview transformed rows and geometry¶
curl \
-H "Authorization: Bearer $GEOJIBE_TOKEN" \
"$GEOJIBE_URL/api/v1/workspaces/<workspace-id>/transform-preview?layer=apiary"
curl \
-H "Authorization: Bearer $GEOJIBE_TOKEN" \
"$GEOJIBE_URL/api/v1/workspaces/<workspace-id>/transform-spatial-preview?layer=apiary"
Deliver a Workspace result¶
curl \
-X POST \
-H "Authorization: Bearer $GEOJIBE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"destination_id":"<destination-id>","destination_target":{"remote_path":"/exports/result.geojson"}}' \
"$GEOJIBE_URL/api/v1/workspaces/<workspace-id>/deliver"
Requires workspaces:run and destinations:read. Convert must have completed
first.