Skip to content

Layers & tables API

The Layers and Tables API allows publishers to set, edit and update layers and tables.

For the full reference, see Layers on apidocs.koordinates.com.

GET/layers/List layers and tables

List layers (at layers/) and tables (at tables/) respectively. List views follow the Catalog API semantics.

Response 200

Headers

Content-Type: application/json

Body

[{
"id": 123,
"url": "https://{domain}/services/api/v1.x/layers/123/",
"type": "layer",
"title": "Fictional Boundary Points",
"first_published_at": "2013-01-01T00:01:01",
"published_at": "2013-12-03T03:56:55Z"
}]
POST/layers/Create a layer or table

title, an owner (group, the ID of the owning group) and data.datasources are required. A request without an owner returns 400 with Either user or group must be supplied. Other fields you can set include description, collected, license, supplier_reference, default_style_id, categories, tags, publish_to_catalog_services, autoupdate, tilegrids, and in data, encoding, crs, elevation_field, primary_key_field and geometry_field. Set publish to true to publish when the import finishes, otherwise the layer is created as a draft. The datasource IDs come from scanning a source.

Request

Headers

Content-Type: application/json

Body

{
"title": "Fictional Boundary Points",
"description": "These points are totally fictional!",
"group": 3,
"categories": ["topographic/nz-topo-offshore-islands-data/terrain"],
"tags": ["tag1", "tag2"],
"publish": true,
"data": {
"datasources": [
{"id": 345}
]
}
}

Response 201

Headers

Content-Type: application/json
Location: https://{domain}/services/api/v1.x/layers/123/

Body: the layer, as in Layer and table detail, with the new version under version (its status is importing until the import finishes). Poll /layers/{id}/versions/{version}/ and wait for status to be ok, which takes about a minute for a tiny layer.

GET/layers/drafts/List layers and tables drafts

Filterable list views of layers (layers/drafts/) and tables (tables/drafts/) respectively, similar to /layers/ and /tables/. This view shows the draft version of each layer or table. If the most recent version of a layer or table has been published already, it won’t appear here.

All the same filters and ordering from layers/ and tables/ apply.

Response 200

Headers

Content-Type: application/json

Body

[{
"id": 123,
"url": "https://{domain}/services/api/v1.x/layers/123/",
"type": "layer",
"title": "Fictional Boundary Points",
"first_published_at": "2013-01-01T00:01:01",
"published_at": "2013-12-03T03:56:55Z"
}]
GET/layers/latest/List latest layers and tables

Lists the latest version of each layer (layers/latest/) or table (tables/latest/), whether or not it’s been published. It works like the catalog’s /data/latest/ view.

POST/layers/validate/Validate a new layer or table

Post the same body you’d send to create a layer or table. Koordinates checks it without creating anything. Use /layers/{id}/versions/validate/ to do the same for a new version of an existing layer or table.

Response 200

{
"feature_count": 3,
"feature_count_estimate": 3,
"diff_summary": {
"added": 1,
"removed": 0,
"sample": {
"added": ["apidoc-test-points.geojson"],
"removed": []
}
}
}

Invalid bodies return 400 with the field errors, as when creating. Validating a new version returns 409 if the layer already has a draft.

GET/layers/{id}/Get a layer or table detail

Displays details of a layer layers/{id}/ or a table tables/{id}/. This view displays the published version only. To get the draft version, use /layers/{id}/versions/draft/ (or /layers/{id}/versions/published/ for the published version). These redirect to the matching version URL, and return 404 if there’s no such version.

Both of these URLs work for either layers or tables. This is because layers can be turned into tables and vice versa. If /tables/123/ is hit for a layer, or /layers/123/ for a table, the behaviour and response will be the same as the “correct” URL, except that the response will contain a Location header specifying the canonical URL. This applies to all sub-urls also (permissions/, metadata/, versions/, etc)

Response 200

Headers

Content-Type: application/json

Body

{
"id": 123,
"url": "https://{domain}/services/api/v1.x/layers/123/",
"type": "layer",
"title": "Fictional Boundary Points",
"first_published_at": "2026-10-16T01:18:00.279642Z",
"published_at": "2026-10-16T01:18:00.279642Z",
"thumbnail_url": "https://tiles-cdn.koordinates.com/…/1200x630.png",
"services": "https://{domain}/services/api/v1.x/layers/123/services/",
"user_capabilities": ["can-star", "can-manage", "can-export"],
"user_permissions": ["find", "view", "download", "admin"],
"description": "These points are totally fictional!",
"description_html": "<p>These points are totally fictional!</p>",
"group": {
"id": 7,
"url": "https://{domain}/services/api/v1.x/groups/7/",
"name": "A group",
"country": "NZ",
"type": "normal"
},
"data": {
"storage": "classic",
"datasources": "https://{domain}/services/api/v1.x/layers/123/versions/456/datasources/",
"datasource_count": 1,
"fields": [
{"name": "GEOMETRY", "type": "geometry"},
{"name": "id", "type": "integer"},
{"name": "name", "type": "string"}
],
"encoding": null,
"crs": {
"id": "EPSG:4326",
"url": "https://{domain}/services/api/v1.x/coordinate-systems/EPSG:4326/",
"name": "WGS 84",
"kind": "geographic 2D",
"srid": 4326
},
"primary_key_fields": [],
"geometry_field": "GEOMETRY",
"geometry_type": "point",
"feature_count": 3,
"extent": {"type": "Polygon", "coordinates": [[[174.76, -36.87], [174.76, -36.85], [174.8, -36.85], [174.8, -36.87], [174.76, -36.87]]]},
"import_started_at": "2026-10-16T01:12:21.000000Z",
"import_ended_at": "2026-10-16T01:13:40.000000Z",
"change_summary": {
"inserted": 3,
"updated": 0,
"deleted": 0,
"schema_changes": {
"added": [],
"changed": [],
"removed": [],
"srid_changed": false,
"geometry_type_changed": false,
"primary_keys_changed": false
}
},
"source_summary": "https://{domain}/services/api/v1.x/layers/123/versions/456/data/source-summary/",
"sample": "https://{domain}/services/api/v1.x/layers/123/versions/456/data/sample/",
"export_formats": [
{"name": "Shapefile", "mimetype": "application/x-zipped-shp"}
]
},
"url_html": "https://{domain}/layer/123-fictional-boundary-points/",
"published_version": "https://{domain}/services/api/v1.x/layers/123/versions/456/",
"latest_version": "https://{domain}/services/api/v1.x/layers/123/versions/456/",
"this_version": "https://{domain}/services/api/v1.x/layers/123/versions/456/",
"kind": "vector",
"categories": [],
"tags": [],
"collected_at": null,
"created_at": "2026-10-16T01:12:20.307643Z",
"license": null,
"metadata": null,
"publish_to_catalog_services": true,
"permissions": "https://{domain}/services/api/v1.x/layers/123/permissions/",
"autoupdate": {"behavior": "not-applicable", "schedule": null, "force_autoupdate": false},
"supplier_reference": "",
"styles": "https://{domain}/services/api/v1.x/layers/123/styles/",
"tilegrids": [],
"default_style": null,
"active_publish": null,
"settings": {"lock_title_to_metadata": false, "lock_description_to_metadata": false, "lock_tags_to_metadata": false},
"version": {
"id": 456,
"url": "https://{domain}/services/api/v1.x/layers/123/versions/456/",
"status": "ok",
"created_at": "2026-10-16T01:12:20.306607Z",
"reference": "",
"progress": 1.0,
"data_import": true,
"data_published_at": "2026-10-16T01:18:00.279642Z"
}
}
DELETE/layers/{id}/Delete a layer or table

Deletes specified layer.

Response 204

A deleted layer returns 410 when you request it.

GET/layers/{id}/versions/List layer or table versions

Filterable list views of versions of a specific layer or table, always sorted newest to oldest.

  • data__source_revision: number (optional). If the version’s source supports revisions, find versions using a specific revision. Values depend on the source type. Optionally use data__source_revision__lt ordata__source_revision__gte to filter using < or >= operators respectively.
  • data_import: boolean (optional). Use data_import=true to list only versions that imported data, and leave out versions that changed only details such as the title or description.

Each version in the default response includes data_published_at, the time its data was first published. Send the header Expand: list to get each layer or table with its version and a data.change_summary of the features inserted, updated and deleted.

Your API key needs the layers:read scope. A key created with Data access doesn’t have it, and requests return Invalid API key scope. See Set scopes for an API token.

Terminal window
curl 'https://{domain}/services/api/v1.x/layers/123/versions/?data_import=true' \
-H "Authorization: key $APITOKEN" \
-H 'Expand: list' \
-H 'Accept: application/json'

Response 200

Headers

Content-Type: application/json

Body

[
{
"id": 456,
"url": "https://{domain}/services/api/v1.x/layers/123/versions/456/",
"status": "importing",
"created_at": "2026-10-16T01:12:20.306607Z",
"reference": "More data",
"progress": 0.5,
"data_import": true,
"data_published_at": null
}
]

With the Expand: list header, each item is instead the layer or table as in Layer and table detail, with the version’s own fields under version and its change summary under data.change_summary.

POST/layers/{id}/versions/Create layer or table version

Creates a new draft version, accepting the same content as POST layers/, except that group isn’t needed. Set the version’s label with reference (not version_reference). If anything in the data object has changed then an import will begin immediately. Otherwise to force a re-import from the previous sources call layers/{id}/versions/{version}/import/.

Request

Headers

Content-Type: application/json

Body

{
"title": "Fictional Boundary Points",
"description": "These points are totally fictional!",
"reference": "added more data",
"data": {
"datasources": [
{"id": 345}
]
}
}

Response 201

Headers

Content-Type: application/json
Location: https://{domain}/services/api/v1.x/layers/123/versions/789/

Body: the layer, as in Layer and table detail, with the new draft under version.

Response 409

Headers

Content-Type: application/json
Location: https://{domain}/services/api/v1.x/layers/123/versions/234/

Body

{"already-exists": "This layer already has a draft version"}
GET/layers/{id}/versions/{version}/Get layer or table version

Get the details for a specific layer or table version.

Response 200

Body: the layer, as in Layer and table detail, for this version, with the version’s own fields under version.

PUT/layers/{id}/versions/{version}/Edit layer or table version

Edits this draft layer version. If the version isn’t a draft, a 405 response will be returned. Accepts same fields as GET above, but ignores the version object. If there is anything in the data object, cancel any existing import and start a new one.

Response 200

Body: the updated layer, as in Layer and table detail. PATCH isn’t supported and returns 405; send the fields you want to change with PUT. Editing the reference takes the reference field.

Response 405

Headers

Content-Type: application/json

Body

{"errors": {"detail": "This version isn't a draft, so you can't edit it"}, "status_code": 405}
DELETE/layers/{id}/versions/{version}/Delete layer or table version

Deletes this draft version (revert to published).

Response 204

Response 405

Headers

Content-Type: application/json

Body

{"not-draft": "This version isn't a draft, so you can't delete it"}
POST/layers/{id}/versions/{version}/import/Start layer or table data import

Starts importing this draft layer version (cancelling any running import), even if the data object hasn’t changed from the previous version.

No POST data.

Response 202

Headers

Location: https://{domain}/services/api/v1.x/layers/123/versions/789/

Body: the layer, as in Layer and table detail.

Response 409

Headers

Content-Type: application/json

Body

{"errors": {"detail": "This version isn't a draft, so you can't import it"}, "status_code": 409}
POST/layers/{id}/versions/import/Create and import a layer or table version

Creates a new draft version from the request body and always starts an import. Takes the same body as creating a version.

POST/layers/{id}/versions/publish/Create and publish a layer or table version

Creates a new version from the request body and publishes it as soon as its import is complete. Returns 409 with {"already-exists": "This layer already has a draft version"} if the layer or table already has a draft version.

POST/layers/{id}/versions/{version}/publish/Publish layer or table version

Creates a publish task just for this version, which publishes as soon as any import is complete.

No POST data.

Response 201

Headers

Location: https://{domain}/services/api/v1.x/publish/456/

Body: the publish task, as in the Publishing API. Its state is waiting-for-items while the version is still importing, and publishing or completed once the import is done.

Response 400

A version that isn’t a draft can’t be published.

{"errors": {"detail": "This version isn't a draft, so you can't publish it"}, "status_code": 400}

A version that already belongs to an active publish task returns 400 with Cannot add version to more than one active publish.

For worked examples, see Admin API recipes.