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.
Layers and tables list
Section titled “Layers and tables list”/layers/List layers and tablesList layers (at layers/) and tables (at tables/) respectively. List views follow the Catalog API semantics.
Response 200
Headers
Content-Type: application/jsonBody
[{ "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"}]/layers/Create a layer or tabletitle, 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/jsonBody
{ "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/jsonLocation: 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.
Layers and table drafts
Section titled “Layers and table drafts”/layers/drafts/List layers and tables draftsFilterable 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/jsonBody
[{ "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"}]Latest layers and tables
Section titled “Latest layers and tables”/layers/latest/List latest layers and tablesLists 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.
Validate a layer or table
Section titled “Validate a layer or table”/layers/validate/Validate a new layer or tablePost 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.
Layer and table detail
Section titled “Layer and table detail”/layers/{id}/Get a layer or table detailDisplays 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/jsonBody
{ "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" }}/layers/{id}/Delete a layer or tableDeletes specified layer.
Response 204
A deleted layer returns 410 when you request it.
Layer and table versions
Section titled “Layer and table versions”/layers/{id}/versions/List layer or table versionsFilterable list views of versions of a specific layer or table, always sorted newest to oldest.
Parameters
Section titled “Parameters”- 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 usedata__source_revision__ltordata__source_revision__gteto filter using<or>=operators respectively. - data_import:
boolean(optional). Usedata_import=trueto 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.
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/jsonBody
[ { "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.
/layers/{id}/versions/Create layer or table versionCreates 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/jsonBody
{ "title": "Fictional Boundary Points", "description": "These points are totally fictional!", "reference": "added more data", "data": { "datasources": [ {"id": 345} ] }}Response 201
Headers
Content-Type: application/jsonLocation: 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/jsonLocation: https://{domain}/services/api/v1.x/layers/123/versions/234/Body
{"already-exists": "This layer already has a draft version"}Layer or table version
Section titled “Layer or table version”/layers/{id}/versions/{version}/Get layer or table versionGet 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.
/layers/{id}/versions/{version}/Edit layer or table versionEdits 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/jsonBody
{"errors": {"detail": "This version isn't a draft, so you can't edit it"}, "status_code": 405}/layers/{id}/versions/{version}/Delete layer or table versionDeletes this draft version (revert to published).
Response 204
Response 405
Headers
Content-Type: application/jsonBody
{"not-draft": "This version isn't a draft, so you can't delete it"}Layer or table import
Section titled “Layer or table import”/layers/{id}/versions/{version}/import/Start layer or table data importStarts 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/jsonBody
{"errors": {"detail": "This version isn't a draft, so you can't import it"}, "status_code": 409}Layer or table create and import
Section titled “Layer or table create and import”/layers/{id}/versions/import/Create and import a layer or table versionCreates a new draft version from the request body and always starts an import. Takes the same body as creating a version.
/layers/{id}/versions/publish/Create and publish a layer or table versionCreates 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.
Layer or table publish
Section titled “Layer or table publish”/layers/{id}/versions/{version}/publish/Publish layer or table versionCreates 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.