Skip to content

Data Sources API

The Data Sources API provides read and write access to sources, datasources and scans.

For the full reference, see Data Sources on apidocs.koordinates.com.

There are several types of sources. Each exposes the same set of core fields, but some fields are only exposed or accepted for specific types of sources. Everything in options is optional.

Info sources merely provide a way to point humans to some data. They are never scanned by Koordinates; they don’t have datasources; and you can’t create layers from them.

You can’t create info sources through the API (info is not a valid type). Existing info sources may still appear in source lists.

All these source types can accept credentials in an options field:

  • options:
    • username : username to access this server.
    • password : password for this server.
    • ssl_verify : A boolean specifying whether Koordinates should verify the SSL certificate of the host when scanning this source. Defaults to true.

Username and password are write-only. They are never returned to clients via the API.

  • url_remote : The URL of your ArcGIS server, usually ending with /rest/services/. Must not contain credentials.

  • options : In addition to the options listed above, ArcGIS also accepts:

    • folders : A JSON array of folders to scan. If not given, the whole server will be scanned.
  • url_remote : A URL of the form “cifs://host/sharename”, optionally with extra path segments. Must not contain credentials.
  • url_remote : A URL of the form “postgresql://host:port/database”. Must not contain credentials.

  • options :

    • username : username to access the PostgreSQL database.
    • password : postgres database password.
    • schemas : A JSON array containing schema names, e.g.: "schemas": ["public", "other_schema"]

A URL source defines an FTP, HTTP or HTTPS url to a data file, or to an archive of data files, that Koordinates can scan.

Whether you can create URL sources depends on your site’s plan. If your plan doesn’t include them, creating one returns 400 with Source type 'url' is not available on this site's plan.

The URL can point straight at a single data file, such as a GeoPackage, CSV or shapefile, in any format Koordinates can import. It can also point at an archive containing data files. These archives are accepted:

  • .zip
  • .tar
  • .tar.gz, .tgz

URL sources accept the following extra fields:

  • url_remote : The URL to download the file or archive from. Must not contain credentials.

When a URL source is scanned again, Koordinates compares the files’ latest modified time and total size with the last scan. For a .zip, that means the files inside it.

  • url_remote : The URL to the WFS server endpoint. Must not contain credentials.

S3 sources don’t use url_remote or options. They take these fields instead:

  • s3_bucket (required): the bucket name.
  • s3_prefix: a key prefix to limit the scan to.
  • s3_role_arn: an IAM role for Koordinates to assume. The read-only s3_external_id field gives the external ID to use in the role’s trust policy.
  • s3_access_key_id and s3_secret_access_key: access keys, as an alternative to a role, where your site allows them. The secret is never returned.
  • s3_endpoint and s3_region: for S3-compatible storage. Not allowed with s3_role_arn.

When an S3 source is scanned again, Koordinates uses each object’s ETag to tell whether it has changed.

GET/sources/List Sources

Returns a list of the sources available to you. The list supports page, page_size and sort (name, -name, created_at) and returns the X-Resource-Range and Link headers described in Introducing the Admin API.

Response 200

Headers

Content-Type: application/json

Body

[
{
"id": 1,
"url": "https://{domain}/services/api/v1.x/sources/1/",
"url_html": "https://{domain}/source/1-harbour-images/",
"title": "harbour-images",
"type": "upload",
"featured_at": null,
"datasource_count": 1,
"user_capabilities": ["can-manage", "can-import"],
"user_permissions": ["find", "view", "create_layers", "admin"]
}
]
POST/sources/Create a Source

To create a source, post to this view.

  • type: string (required). The type of source to create. Example: upload. Choices: upload url arcgis s3 postgres wfs cifs. Which types you can create depends on your site’s plan.
  • title: string (required). The name of the new source. (This is returned as title too.)
  • group: string|id (required). The group that owns the source, by ID or API URL. Sources can only be owned by groups. Sending user returns Only groups can own this object.
  • description: string (optional).
  • categories: array (optional).
  • url_remote: string (optional). The remote URL to the source, not required for upload or S3 sources
  • options: object (optional). Refer to the source specific notes above.
  • scan_schedule: string (optional). A Cron expression specifying how frequently the source should be scanned. All normal cron values are supported but the ‘minutes’ value is currently ignored. Predefined scheduling definitions are supported except for “@reboot”.

Request

Headers

Content-Type: application/json

Body

{
"title": "Bob's ArcGIS 10 Server",
"type": "arcgis",
"description": "All of Bob's data",
"categories": [],
"group": 3,
"options": {
"username": "bob",
"password": "sekret_p@55w0rd"
},
"url_remote": "https://bob.example.com/ArcGIS/rest/services",
"scan_schedule": "@daily"
}

Response 201

Headers

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

Body: the source detail, as in Source detail.

POST/sources/Create an upload source

To upload files to the API, use the multipart/form-data content-type instead of JSON.

Your multipart request should include one part with name="source", which contains the JSON object with the required fields (title, type and group), as per above. Other parts should include their filename in the Content-Disposition header.

Each file you upload can either be a normal file, or an archive containing more files. We currently support these archive types:

  • .zip
  • .tar.gz, .tgz
  • .7z

Request

Headers

Content-Type: multipart/form-data; boundary=b0017bc3bab14fed95910bfcc4a1ddd8

Body

--b0017bc3bab14fed95910bfcc4a1ddd8
Content-Disposition: form-data; name="abc"; filename="points-of-interest.zip"
Content-Type: application/x-zip
(binary data not shown)
--b0017bc3bab14fed95910bfcc4a1ddd8
Content-Disposition: form-data; name="source"
Content-Type: application/json
{
"title": "points-of-interest",
"type": "upload",
"group": 3
}
--b0017bc3bab14fed95910bfcc4a1ddd8--

Response 201

Headers

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

Body: the source detail. A scan starts automatically for multipart uploads.

Instead of sending files in a multipart request, you can create an upload source with a JSON body that lists the file names in files, for example {"title": "roads", "type": "upload", "group": 3, "files": ["roads.zip"]}. To split a file into parts, add the part count in square brackets, for example "roads.zip[10]". Each entry in the response’s files list has a filename, a state (PENDING until you complete it) and parts, a presigned URL for each part.

  1. PUT each part to its presigned URL with no Content-Type header (a Content-Type that wasn’t signed, such as the application/x-www-form-urlencoded that some clients add, fails with SignatureDoesNotMatch), and keep the ETag header from each response.

  2. Complete each file by posting its ETags, in part order, as {"etags": [...]} to /sources/{id}/uploads/{filename}/. The response is the file with state set to COMPLETED.

  3. Start a scan with POST /sources/{id}/scans/. Presigned uploads don’t start a scan automatically.

GET/sources/{id}/uploads/List a source's presigned uploads

Each item has filename, state and pre_signed_urls.

POST/sources/{id}/uploads/{filename}/Complete a presigned upload
GET/sources/{id}/Get source detail

Response 200

Body

{
"id": 1,
"url": "https://{domain}/services/api/v1.x/sources/1/",
"url_html": "https://{domain}/source/1-harbour-images/",
"title": "harbour-images",
"type": "upload",
"featured_at": null,
"datasource_count": 1,
"user_capabilities": ["can-manage", "can-import"],
"user_permissions": ["find", "view", "create_layers", "admin"],
"license": null,
"publish_to_catalog_services": false,
"permissions": "https://{domain}/services/api/v1.x/sources/1/permissions/",
"description": "Upload: harbour-images",
"description_html": "<p>Upload: harbour-images</p>",
"categories": [],
"tags": [],
"group": {
"id": 3,
"url": "https://{domain}/services/api/v1.x/groups/3/",
"name": "Bob Jones and Associates",
"country": "NZ",
"type": "normal"
},
"metadata": null,
"last_scanned_at": null,
"scan_schedule": null,
"options": {},
"datasources": "https://{domain}/services/api/v1.x/sources/1/datasources/",
"scans": "https://{domain}/services/api/v1.x/sources/1/scans/",
"last_scan": null,
"settings": {
"lock_title_to_metadata": false,
"lock_description_to_metadata": false,
"lock_tags_to_metadata": false
},
"files": [
{
"filename": "harbour-images.zip",
"parts": ["https://presigned-upload-url.example.com/…"],
"state": "COMPLETED",
"expires_at": "2026-10-17T01:11:19.697872Z"
}
]
}
PUT/sources/{id}/Edit a source

Note that:

  • You can’t change the type of a source.
  • You can’t upload more files into an upload source. If you need to upload more files you should create a new source.

The following fields are editable (other fields will be ignored):

  • title
  • description
  • categories
  • group
  • scan_schedule
  • options (note: existing options will be preserved if they’re not specified here)

Request

Headers

Content-Type: application/json

Body

{
"description": "Harbour Imagery (4m/px)"
}

Response 200

The updated source detail, as above.

DELETE/sources/{id}/Delete a source

Response 204. A deleted source returns 404.

A ‘scan’ occurs when we inspect a source to find content that can be used to create layers, tables, and documents.

GET/sources/{id}/scans/List scans

Returns a list of scans for a source.

Notable fields:

  • status : The current status of this scan. Valid values are:

    • running
    • completed
    • cancelled
    • error
  • change_counts : counts of datasources deleted, updated and added in this scan. This is only guaranteed to be present in completed scans. For other scans it may be null or the counts may all be 0.

  • last_activity_at : the last time the scan reported activity.

  • source : the API URL of the source.

Response 200

Headers

Content-Type: application/json

Body

[
{
"id": 4,
"url": "https://{domain}/services/api/v1.x/sources/1/scans/4/",
"status": "completed",
"started_at": "2026-10-16T01:11:38.937303Z",
"completed_at": "2026-10-16T01:11:52.730702Z",
"last_activity_at": "2026-10-16T01:11:52.730825Z",
"change_counts": {
"added": 1,
"deleted": 0,
"updated": 0
},
"log": "https://{domain}/services/api/v1.x/sources/1/scans/4/log/",
"source": "https://{domain}/services/api/v1.x/sources/1/"
}
]
POST/sources/{id}/scans/Start scan

Starts a scan for this source. No post data is required. The scan runs in the background: the 201 response has status of running, and the Location header is the scan URL. Poll the scan until status is completed, cancelled or error.

Response 201

Headers

Content-Type: application/json

Body

{
"id": 4,
"url": "https://{domain}/services/api/v1.x/sources/1/scans/4/",
"status": "running",
"started_at": "2026-10-16T01:11:38.937303Z",
"completed_at": null,
"last_activity_at": "2026-10-16T01:11:52.730825Z",
"change_counts": {
"added": 0,
"deleted": 0,
"updated": 0
},
"log": "https://{domain}/services/api/v1.x/sources/1/scans/4/log/",
"source": "https://{domain}/services/api/v1.x/sources/1/"
}
GET/sources/{id}/scans/{scan}/Get scan detail

Information about a particular scan. This is the same as an item in the list view above, except it also includes a progress indicator, which is a float between 0 and 1. Progress is approximate.

Response 200

Headers

Content-Type: application/json

Body

{
"id": 4,
"url": "https://{domain}/services/api/v1.x/sources/1/scans/4/",
"status": "completed",
"started_at": "2026-10-16T01:11:38.937303Z",
"completed_at": "2026-10-16T01:11:52.730702Z",
"last_activity_at": "2026-10-16T01:11:52.730825Z",
"change_counts": {
"added": 1,
"deleted": 0,
"updated": 0
},
"log": "https://{domain}/services/api/v1.x/sources/1/scans/4/log/",
"source": "https://{domain}/services/api/v1.x/sources/1/",
"progress": 1.0
}
DELETE/sources/{id}/scans/{scan}/Cancel or delete scan

Cancels an in-progress scan.

Response 204

GET/sources/{id}/scans/{scan}/log/Get scan log

Gets a log for a scan. It’s returned as text/plain when you send Accept: text/plain. With Accept: application/json the whole log is returned as one JSON string.

Response 200

Headers

Content-Type: text/plain

Body

2012-11-14 15:53:28: Scraping ArcGIS server at https://bob.example.com/ArcGIS/rest/services
2012-11-14 15:53:29: Looking for layers in folder: Bob/MapServer
2012-11-14 15:53:29: Requesting access to https://bob.example.com/ArcGIS/rest/services/
2012-11-14 15:53:29: get: https://bob.example.com/ArcGIS/rest/services/Bob/MapServer?f=json
2012-11-14 15:53:29: crawled in 0.445s
2012-11-14 15:53:29: Looking in folder: Bob/MapServer
2012-11-14 15:53:29: Found layer: 0 - 'Null Island Polygons'
2012-11-14 15:53:29: get: https://bob.example.com/ArcGIS/rest/services/Bob/MapServer/0?f=json
2012-11-14 15:53:30: crawled in 0.447s
2012-11-14 15:53:30: get: https://bob.example.com/ArcGIS/rest/services/Bob/MapServer/0/query?returnGeometry=false&outFields=%2A&returnIdsOnly=true&where=1%3D1&f=json
2012-11-14 15:53:31: crawled in 0.601s
2012-11-14 15:53:47: Finishing up
2012-11-14 15:53:47: Updating 1 new or changed datasets
2012-11-14 15:53:47: Updating user interface
2012-11-14 15:53:47: Finished!
GET/sources/{id}/tree/Get the source's file tree

Returns the tree of folders, files, datasources and metadata found by scanning the source, with counts at each node. Add a path after tree/ to get a node further down the tree, for example /sources/{id}/tree/folder/file.gpkg/.

The API for datasources is read-only. Datasources are updated by scanning your source. If you don’t see what you expect here, check that your source has been scanned recently.

GET/sources/{id}/datasources/List datasources

Lists datasources for this source.

Response 200

Headers

Content-Type: application/json

Body

[
{
"id": 1,
"url": "https://{domain}/services/api/v1.x/sources/1/datasources/1/",
"title": "apidoc test points",
"data_type": "vector",
"data_type_label": "Vector",
"format": "application/json",
"geometry_type": "point",
"updated_at": "2026-10-16T01:11:40.461348Z",
"state": "new",
"type": "upload",
"upload_path": "apidoc-test-points.geojson"
}
]
GET/sources/{id}/datasources/{datasource}/Get datasource detail

Detailed information for a given datasource. Includes all the info shown in the list above, as well as much more information describing the datasource.

Field notes

  • type is the source type, not to be confused with data_type, format or geometry_type

  • state is the datasource’s state, for example new

  • layer links to the layer created from this datasource, or is null

  • data_type refers to the broad type of data. Different types of data require significantly different handling. Current data types are:

    • table
    • vector
    • raster
    • grid
    • attribute-grid
  • format is a string describing the media type of this datasource.

  • geometry_type describes the type of geometry in vector datasources. It is null for non-vector datasources. Possible values are:

    • point
    • linestring
    • polygon
    • multipoint
    • multilinestring
    • multipolygon
  • Spatial datasources may or may not have a known Coordinate Reference System (crs).

  • extent is a GeoJSON geometry. It may be null for aspatial datasources. The coordinate system for this geometry is the same as that in the crs field, which may not be known.

  • For vector and table datasources, fields is a list of JSON objects. It will be null for other datasources.

Response 200

Headers

Content-Type: application/json

Body

{
"id": 1,
"url": "https://{domain}/services/api/v1.x/sources/1/datasources/1/",
"title": "apidoc test points",
"data_type": "vector",
"data_type_label": "Vector",
"format": "application/json",
"geometry_type": "point",
"updated_at": "2026-10-16T01:11:40.461348Z",
"state": "new",
"type": "upload",
"crs": "EPSG:4326",
"fields": [
{"name": "GEOMETRY", "type": "geometry"},
{"name": "id", "type": "integer"},
{"name": "name", "type": "string"}
],
"num_features": 3,
"num_bands": null,
"has_z": false,
"extent": {
"type": "Polygon",
"coordinates": [
[
[174.76, -36.87],
[174.76, -36.85],
[174.8, -36.85],
[174.8, -36.87],
[174.76, -36.87]
]
]
},
"source": "https://{domain}/services/api/v1.x/sources/1/",
"metadata": null,
"metadata_resource": null,
"description": "",
"geotags": "https://{domain}/services/api/v1.x/sources/1/datasources/1/geotags/",
"upload_path": "apidoc-test-points.geojson",
"layer": null
}

Other fields depend on the source type: for example ArcGIS datasources include arcgis_folder_service_path, arcgis_geometry_type, arcgis_layer_name and arcgis_type.

GET/sources/{id}/datasources/{datasource}/metadata/Get datasource metadata

Some datasources can have attached XML metadata. If a datasource has none, this returns 404. This is attached by the CIFS and Upload scanners, if a datasource has an .xml file with the same basename. For example:

my-points-with-metadata.geojson
my-points-with-metadata.xml

As with sources, datasource XML must be in one of these formats (sources and datasources, like documents and sets, accept ISO 19115/19139 and Dublin Core; FGDC is accepted for layers and tables):

  • ISO 19115/19139
  • FGDC CSDGM
  • Dublin Core

Response 200

Body

<dc xmlns:oai_dc="http://www.openarchives.org/OAI/2.0/oai_dc/">
<title></title>
<creator>Bob Jones and Associates</creator>
<subject>hydro</subject>
<description>Some nice colourful aerial photos of the Auckland Harbour.</description>
<date>2013-01-01</date>
<type>raster</type>
<language>eng</language>
</dc>