API reference
Sarkoa answers HTTP on two hosts, and picking the wrong one is the most common way to lose an afternoon. They are not tiers of the same API; they do different jobs.
flowchart LR
Client[Your app or script]
Client -->|Bearer sk_...| PublicAPI[Public API host]
Client -->|Bearer API key| AppHost[App host]
PublicAPI --> Tiles[Tiles and metadata]
AppHost --> Library[Files, maps, series]
AppHost --> Data[Feature query and catalogue]
AppHost --> Hooks[Webhooks]
Which host serves what
| You want | Host | Auth | Plan |
|---|---|---|---|
| Tiles and dataset metadata for your own map client | api.sarkoa.com |
Asset API key (sk_β¦), from API Routes |
Essential and above |
| Upload, list, move, download, delete files | app host, /api/β¦ |
API key from Profile β API & Integrations | Any |
| Create and edit custom maps | app host, /api/β¦ |
Same | Any |
| Query features and search the raster catalogue | app host, /api/β¦ |
Same | Any |
| Webhooks | app host, /api/β¦ |
Same | Essential and above |
The two key types are separate and not interchangeable. An Asset API key will not manage files, and a Profile key will not fetch tiles from api.sarkoa.com. See Authentication.
Most people never call the app host by hand. The CLI, QGIS plugin, and MCP are all clients of it, and they handle presigned uploads, paging, and retries for you. Reach for raw HTTP when you are embedding Sarkoa in something those cannot cover.
The public API
- Hostname:
https://api.sarkoa.com - Header:
Authorization: Bearer sk_... - Query alternative:
?api_key=sk_..., for contexts where you cannot set a header (an<img>tag, some GIS clients). Prefer the header: query strings end up in logs, proxies, and browser history. - Scopes:
tiles:read,metadata:read - Methods:
GETandHEADonly, plusOPTIONSfor CORS. Anything else returns405.
Machine-readable spec
The full OpenAPI 3.0 document is public and needs no key to read:
https://app.sarkoa.com/openapi/sarkoa-public-api.json
Point a generator, an HTTP client, or an agent at it rather than transcribing this page. Signed in, the web app also renders it as interactive docs with your own organization and dataset slugs filled in.
Routes
GET /health # liveness, no API key
By file ID:
GET /v1/assets/:assetId/tiles/:z/:x/:y.webp # raster tile
GET /v1/assets/:assetId/vector/tiles/:z/:x/:y.mvt # vector tile
GET /v1/assets/:assetId/metadata # asset metadata JSON
By org and dataset slug, where datasetSlug is a name you configure under API Routes:
GET /v1/:orgSlug/:datasetSlug/tiles/:z/:x/:y.webp
GET /v1/:orgSlug/:datasetSlug/vector/tiles/:z/:x/:y.mvt
GET /v1/:orgSlug/:datasetSlug/metadata
Raster tiles are served as WebP. There is no .png route. Calling the raster path for a vector file, or the reverse, returns 400 with wrong_asset_type rather than an empty tile.
The spec also describes temporal tile and manifest routes. Those are turned off on the public API and answer 503 with feature_disabled; time-based layers are served through the app and the map viewer today.
Errors
Every error carries a human-readable error and a stable code. Match on code, never on the message.
{ "error": "Public API access requires an Essential plan or higher.", "code": "plan_upgrade_required" }
| Status | Common codes | Meaning |
|---|---|---|
400 |
invalid_asset_id, invalid_time, wrong_asset_type |
Malformed request, or the wrong tile family for this file type |
401 |
api_key_required, invalid_api_key, api_key_expired |
Key missing, unrecognized, or past its expiry date |
403 |
insufficient_scope, plan_upgrade_required |
Key lacks the scope, or the plan is below Essential |
404 |
asset_not_found, route_not_found, not_found, no_snapshot |
Unknown, deleted, archived, or belonging to another organization |
429 |
quota_exceeded |
Over tile quota. Honour the Retry-After header |
503 |
feature_disabled |
The API or that tile family is turned off |
Deleted and archived files stop serving immediately and return 404. An asset belonging to another organization also returns 404 rather than 403, so a key cannot be used to probe what exists elsewhere.
Caching
Tiles and metadata for private files are returned as Cache-Control: private, so they are stored only by the requesting client. Files shared with a public link are returned as public and may be cached by shared proxies and CDNs.
Billing notes
- For files shared with a public link, tile usage is billed to the organization that hosts the file. Otherwise it is billed to the organization the API key belongs to
More on usage: Tile requests.
Related
- Authentication: the two key types, and their scopes
- Feature query: asking which features match, not just drawing them
- Webhooks: being told when data lands, instead of polling
- CLI: upload, list, and download from the terminal
- MCP: the same surface for AI agents
- Tile requests: how tile fetches relate to billing