Exporting layers to files
Any cached layer can be downloaded as a file. Exports run as background jobs, so a large base converts without tying up the API, and one request can produce several formats at once.
The endpoint is shaped like Esri’s — POST .../FeatureServer/export, returning an
esriJob… status document — so ArcGIS clients that already speak createReplica need no
special handling.
1. From the UI
Section titled “1. From the UI”Open a base and pick the Export tab. Choose one or more formats, tick the layers you want, optionally set a projection or a filter, and hit Export. A progress card appears and turns into a row of download buttons when the job lands.
Downloads expire (24 hours by default, see Expiry), so treat the links as short-lived rather than permanent URLs.
2. Over REST
Section titled “2. Over REST”curl -X POST 'https://{SERVER}/api/v0/rest/services/{baseId}/FeatureServer/export' \ -H 'Authorization: Bearer {key}' \ -H 'Content-Type: application/json' \ -d '{ "formats": "gpkg,shapefile" }'{ "jobId": "6f1c…", "jobStatus": "esriJobSubmitted", "statusUrl": "https://{SERVER}/api/v0/rest/services/{baseId}/FeatureServer/export/jobs/6f1c…"}Authenticate exactly as you would a FeatureServer read: a session cookie, an org- or base-scoped API key, or an app token. Parameters may be sent as JSON body fields or as query-string parameters — ArcGIS clients use both, so either works.
| Path | Exports |
|---|---|
/rest/services/{baseId}/FeatureServer/export |
every table in the base |
/rest/services/{baseId}/FeatureServer/export?layers=0,2 |
just those layer ids |
/rest/services/{baseId}/{tableName}/FeatureServer/export |
that one table |
POST …/createReplica is an alias of …/export, for clients that only know the Esri name.
Poll for the result
Section titled “Poll for the result”curl 'https://{SERVER}/api/v0/rest/services/{baseId}/FeatureServer/export/jobs/{jobId}' \ -H 'Authorization: Bearer {key}'{ "jobId": "6f1c…", "jobStatus": "esriJobSucceeded", "progress": 100, "expires": "2026-09-10T14:02:11.000Z", "results": { "paths": [ { "format": "gpkg", "sizeBytes": 1841152, "url": "…/jobs/6f1c…/results/gpkg" }, { "format": "shapefile", "sizeBytes": 903221, "url": "…/jobs/6f1c…/results/shapefile" } ] }}jobStatus is one of esriJobSubmitted, esriJobExecuting, esriJobSucceeded or
esriJobFailed. Each paths entry is a direct download — same auth as everything else.
A job that produced some of the formats it was asked for still succeeds, and reports the
rest under messages:
"messages": [{ "type": "esriJobMessageTypeError", "description": "dxf: ogr2ogr exited 1 …" }]So always read messages even on esriJobSucceeded — never assume every requested format
is in results.
3. Formats
Section titled “3. Formats”Request them as a comma-separated formats list (or repeat format=).
Common
Section titled “Common”format |
File | Notes |
|---|---|---|
geojson |
.geojson |
Copied straight from the cache when unfiltered, so it is essentially free |
csv |
.csv |
Geometry written as WKT |
xlsx |
.xlsx |
One sheet per layer |
shapefile |
.zip |
Field names truncated to 10 characters by the format itself |
gpkg |
.gpkg |
One table per layer |
kml / kmz |
.kml / .kmz |
One folder per layer |
format |
File | Notes |
|---|---|---|
filegdb |
.zip |
A .gdb folder, one feature class per layer; opens in ArcGIS Pro |
featurecollection |
.json |
Esri FeatureSet JSON with a layerDefinition |
spatialite |
.sqlite |
SpatiaLite database — Esri’s “SQLite geodatabase”; QGIS reads it directly |
Interchange
Section titled “Interchange”format |
File | Notes |
|---|---|---|
flatgeobuf |
.fgb |
Cloud-optimized; seekable and streamable |
geoparquet |
.parquet |
Columnar, for DuckDB / Spark / pandas analytics |
gml |
.gml |
OGC XML, common in European open data |
dxf |
.dxf |
CAD exchange (AutoCAD); attributes are largely dropped by the format |
Vector tiles
Section titled “Vector tiles”mbtiles builds a vector tileset rather than converting features, and takes its own
options (zoom range, per-layer field keep-lists, projection) plus an optional snapshot
block that publishes it at a hosted tiles endpoint. It does not accept the filters
below. See the Vector tiles tab in the UI, and
Vector tiles and snapshots for the full option set.
One file or many?
Section titled “One file or many?”gpkg, filegdb, xlsx, kml, kmz, gml and spatialite hold many layers natively,
so a multi-layer export is a single file with a layer/table/sheet per table.
The rest hold one layer each, so a multi-layer export of geojson, csv, shapefile,
featurecollection, flatgeobuf, geoparquet or dxf arrives as a .zip of
per-layer files. A single-layer export is always the plain file.
4. Filters
Section titled “4. Filters”All four are optional, all apply to every layer in the export, and all are evaluated by GDAL:
| Parameter | Example | Effect |
|---|---|---|
outSR |
3857 |
Reproject from 4326 to this EPSG code |
where |
population > 100000 |
SQL attribute filter |
bbox |
-10,40,5,55 |
Spatial filter, minX,minY,maxX,maxY in 4326 |
outFields |
name,population,iso3 |
Keep only these attributes (* or omitted keeps all) |
curl -X POST '…/{baseId}/FeatureServer/export' \ -H 'Authorization: Bearer {key}' -H 'Content-Type: application/json' \ -d '{ "formats": "geojson,gpkg", "layers": "0,1", "outSR": 3857, "where": "population > 100000", "outFields": "name,population" }'Because the filter is shared, a where clause or a field name that only exists on some of
the selected layers will fail those layers — they show up in messages while the rest of
the export still succeeds.
bbox is always interpreted in 4326, even when outSR asks for something else: it filters
the source data, then the survivors are reprojected.
5. Expiry
Section titled “5. Expiry”Artifacts live on disk under the export id and are swept by an hourly maintenance job.
The window is EXPORT_TTL_HOURS (default 24); the exact deadline for a given job is in
its status document as expires. After that both the download and the status endpoint
return 404, and a new job has to be submitted.
6. Exports read the cache
Section titled “6. Exports read the cache”An export converts whatever Sync Server last synced, not the upstream source. A layer with
no cache yet fails with No cache for layer "…" — sync the base first., so trigger a sync
(POST /api/v0/bases/{baseId}/sync) and wait for it before exporting a newly added table.