Skip to main content

Data API

Push GeoJSON or CSV datasets directly into a project from an external pipeline — no UI clicks, no manual import. A single HTTP request creates or replaces a layer; subsequent calls with the same name overwrite in place.

The Data API is intended for automated systems (CI jobs, scheduled scripts, monitoring pipelines) that need to keep a project's data in sync with an external source. For one-off uploads, use the in-app import flow instead.

Endpoint

POST /functions/v1/collections    — create or replace a layer
GET /functions/v1/collections — list API-managed layers in the project

The base URL is your Supabase project URL — e.g. https://<project-ref>.supabase.co/functions/v1/collections.

Authentication

Every request needs an opaque bearer token:

Authorization: Bearer gp_<your-token>

Tokens start with the literal prefix gp_ (GeoPlanning) followed by 32 random bytes encoded as base64url. They are stored as a SHA-256 hash on the server — the raw value is shown to you once, when the token is created. If you lose it, generate a new one and revoke the old one.

Generating a token

The Data API tab in Organisation Settings, showing the token list and the New token button

Tokens are org-scoped: a single token can push data into any project in your organisation. Specify which project a request targets by including project_id in the request body (see Org-scoped tokens below).

To generate a token:

  1. Open your Organisation Settings and click the Data API tab.

  2. Click New token.

    The Generate token dialog with label, scope, and optional expiry date fields

  3. Give it a label (e.g. nightly-build), pick a scope (write for pushing data, read for listing only), and an optional expiry date.

  4. Click Generate token.

    The token created dialog showing the one-time gp_ prefixed token value and an example curl request

  5. Copy the token value now — it will not be shown again.

Tokens are visible to org owners and admins only. Plain org members, and project editors/contributors who are not org owners or admins, cannot view or manage Data API tokens.

Revoking a token

In the same settings panel, click Revoke next to the token. Any pipeline using it will start receiving 401 ERR_INVALID_TOKEN immediately.

Pushing data — POST /collections

Two body shapes are supported. Both require a name field — the layer is identified in the project by that name, and subsequent calls with the same name upsert in place.

GeoJSON body

curl -X POST https://<project-ref>.supabase.co/functions/v1/collections \
-H "Authorization: Bearer gp_<your-token>" \
-H "Content-Type: application/json" \
-d '{
"name": "Parking sensors",
"geojson": {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": { "type": "Point", "coordinates": [10.75, 59.91] },
"properties": { "id": "p-001", "occupied": true }
}
]
}
}'

The geojson value must be a FeatureCollection with at least one Feature. Each feature must have a geometry.

CSV body

curl -X POST https://<project-ref>.supabase.co/functions/v1/collections \
-H "Authorization: Bearer gp_<your-token>" \
-H "Content-Type: application/json" \
-d '{
"name": "City offices",
"format": "csv",
"data": "lat,lon,name\n59.91,10.75,Oslo\n55.67,12.56,Copenhagen"
}'

The CSV must have a header row. The Data API automatically detects the coordinate columns by header name (case-insensitive):

  • Latitude: lat, latitude, y, northing
  • Longitude: lon, lng, longitude, x, easting

Rows where lat or lon cannot be parsed as a number are silently skipped. All other columns become feature properties. Quoted fields with embedded commas are supported (e.g. "Smith, John"); double quotes inside a quoted field are escaped as "".

Targeting a project

Every token is org-scoped, so include project_id in the body to tell the API which project the layer belongs to:

curl -X POST https://<project-ref>.supabase.co/functions/v1/collections \
-H "Authorization: Bearer gp_<your-token>" \
-H "Content-Type: application/json" \
-d '{
"name": "Air quality stations",
"project_id": "00000000-0000-0000-0000-000000000000",
"geojson": { "type": "FeatureCollection", "features": [...] }
}'

The project must belong to the same organisation as the token, otherwise the request fails with 403 ERR_PROJECT_NOT_IN_ORG.

Response

{
"success": true,
"layer_id": "00000000-0000-0000-0000-000000000000",
"created": true
}
FieldMeaning
layer_idStable UUID of the layer. Same value on subsequent updates of the same name.
createdtrue if a new layer was created, false if an existing layer was replaced.

Status code is 201 on create, 200 on replace.

Listing layers — GET /collections

curl -X GET https://<project-ref>.supabase.co/functions/v1/collections \
-H "Authorization: Bearer gp_<your-token>"

For org-scoped tokens, pass the project ID as a query parameter:

curl -X GET "https://<project-ref>.supabase.co/functions/v1/collections?project_id=<uuid>" \
-H "Authorization: Bearer gp_<your-token>"

Response:

{
"success": true,
"items": [
{
"id": "00000000-0000-0000-0000-000000000000",
"name": "Parking sensors",
"type": "geojson",
"created_at": "2026-04-29T08:31:00Z",
"updated_at": "2026-04-29T10:14:00Z"
}
]
}

Only layers created via the Data API are returned — manually-imported layers are excluded.

Error codes

All errors return JSON of shape { "success": false, "message": "ERR_*" } with an HTTP status:

StatusCodeMeaning
400ERR_NAME_REQUIREDname field missing or empty.
400ERR_INVALID_BODYRequest body is not valid JSON.
400ERR_PROJECT_REQUIREDOrg-scoped token without project_id in body or query.
400ERR_GEOJSON_NULLgeojson is null or not an object.
400ERR_GEOJSON_MISSING_TYPEGeoJSON has no type field.
400ERR_GEOJSON_INVALID_TYPEGeoJSON type is not FeatureCollection.
400ERR_GEOJSON_MISSING_FEATURESNo features array.
400ERR_GEOJSON_EMPTY_FEATURESEmpty features array.
400ERR_GEOJSON_FEATURE_MISSING_TYPEA feature has no type.
400ERR_GEOJSON_FEATURE_INVALID_TYPEA feature's type is not Feature.
400ERR_GEOJSON_FEATURE_MISSING_GEOMETRYA feature has no geometry.
400ERR_CSV_NO_COORDINATESCSV has no detectable lat/lon columns.
401ERR_INVALID_TOKENToken missing, malformed, not found, or expired.
403ERR_FORBIDDENToken has read scope only (POST requires write).
403ERR_PROJECT_NOT_IN_ORGOrg-scoped token, but project_id belongs to a different org.
413ERR_PAYLOAD_TOO_LARGERequest body exceeds 25 MB.
500ERR_INTERNAL / ERR_STORAGE_UPLOAD / ERR_INSERT / ERR_UPDATEServer-side failure — retry safe.

Limits

LimitValue
Maximum request body size25 MB
Rate limitingNone in v1 — please be reasonable. May be added later.
Layer name lengthSame as the in-app layers panel (no specific cap enforced by the API itself).
Token expiryOptional. If set, requests with the token after the expiry date return 401 ERR_INVALID_TOKEN.

Migration from the old /public/v1/collections API

If you are coming from the previous platform's RS256 JWT-based collections API:

OldNew
EndpointPOST /public/v1/collectionsPOST /functions/v1/collections
TokenRS256 JWT with ws + scope claimsOpaque gp_<token>
Token sourceBackend's JwtGeneratorServiceOrganisation Settings → Data API tab
Workspace bindingws claim in JWTEncoded in the token record
Name conflict400 ERR_NAME_TAKENUpserts in place — no error
List endpointGET /v1/assets/datasetsGET /functions/v1/collections
List response keyitems[].publicIditems[].id

The Authorization: Bearer … header pattern and the request body shape ({ name, geojson } and { name, format, data }) are unchanged. Typical migration is a 3-line diff: new endpoint URL, new token, drop any workspace_id field from the body.