Bring your own data
Private overlays live under your account prefix on R2 and paint on top of the Canada basemap and the industry catalogs. The file is yours to prepare. unmap stores it and draws it. It does not reproject, index, search, or identify it.
Limits
| Cap | Value |
|---|---|
| Overlays per account | 10 |
| Size per overlay | 5 MB |
| Formats | GeoJSON (FeatureCollection, Feature, or a geometry) and PMTiles |
| Auth | API key on /byod, session cookie on /account/byod |
A larger file or an 11th overlay is 400 bad_request. An overlay stays until you delete it.
Keys on the same account share the ten slots. A key with no account keeps its own files, and
the dashboard lists the account, so those files do not appear there. This is included with API
access. It is not a separate add-on, and each request counts as a byod call.
Upload, list, delete
curl -X POST "https://api.unmap.dev/byod?name=leases" \
-H "Authorization: Bearer $UNMAP_API_KEY" \
-H "Content-Type: application/geo+json" \
--data-binary @leases.geojson{
"id": "ovl_aabbccddeeff",
"name": "leases",
"kind": "geojson",
"bytes": 1284,
"createdAt": "2026-09-11T18:00:00.000Z",
"url": "https://api.unmap.dev/byod/ovl_aabbccddeeff/data",
"overlay": "byod:ovl_aabbccddeeff"
}GET /byod lists your overlays and repeats the limits. DELETE /byod/{id} removes the object.
GET /byod/{id}/data is the file itself (same key). Multipart file + optional name is also
accepted. The dashboard data tab uploads, lists, and deletes over the account session. The
map still loads the file with your API key.
There is no replace. Uploading again mints a new id, and deleting the previous overlay frees
the slot. A style that still names the old id is a 400.
Objects are stored at byod/{accountId}/{id}. Another account cannot list, read, or delete
yours: the answer is 404, not a confirmation that the id exists.
From the SDK
import { Unmap } from "@unmap/sdk";
const unmap = new Unmap({ key: process.env.UNMAP_API_KEY! });
const input = document.querySelector<HTMLInputElement>("#overlay")!;
const { overlay } = await unmap.byod.upload(input.files![0]!, { name: "Service areas" });
const map = new Unmap({ key: process.env.UNMAP_API_KEY!, container: "#map", overlays: [overlay] });
const { overlays, limits } = await unmap.byod.list();
await unmap.byod.delete("ovl_aabbccddeeff");upload() returns the whole record: overlay is the byod:<id> token that overlays takes,
not the bare id. Pass a File straight from an input and the runtime handles the multipart
encoding. A plain object goes as GeoJSON, and raw bytes go as PMTiles unless you pass
{ kind: "geojson" }. A refused upload throws a CatalogError with status 400 and code
bad_request, carrying the server's own reason. Uploads and deletes are never retried, so a
lost response cannot store the same file twice.
The file
Send GeoJSON as longitude, latitude, in degrees. unmap does not reproject. A projected file is stored unchanged and drawn in the wrong place.
The upload accepts the body when the JSON parses and its top-level type is a GeoJSON type, or
when a PMTiles file begins with the PMTiles header. It does not check that the coordinates
are numbers, or that they fall inside Canada.
MapLibre downloads the whole GeoJSON when the map loads. Five megabytes is the entire file. A larger area needs a PMTiles archive, which you add to the map yourself.
Paint it on a style
GeoJSON overlays compose through the existing overlays= hook:
GET /styles/muted.json?overlays=byod:ovl_aabbccddeeff,municipalities
overlays= replaces profile. List every catalog layer you still want in the same parameter.
const map = new Unmap({
key,
style: "muted",
overlays: ["byod:ovl_aabbccddeeff", "municipalities"],
});unmap adds a GeoJSON source pointing at /byod/{id}/data?key= and two layers, above roads and
below labels, in the style's boundary color:
PointandMultiPointare circles.LineString,MultiLineString,Polygon, andMultiPolygonare strokes. There is no fill.- A
GeometryCollectionmatches neither layer, so it is stored and not drawn. - Attributes stay in the file. These layers do not read them, so nothing is labeled and nothing is colored from a property.
Your API key is in that data URL. MapLibre fetches the file from the browser and cannot send
an Authorization header, so treat the style document as a credential. A missing id is a
400. Deleting an overlay does not rewrite a style that still names it; that request stays
400 until you drop the token.
PMTiles
A PMTiles upload is stored and served at /byod/{id}/{z}/{x}/{y}?key=. Add the vector source
yourself and name its source-layer. Style composition will not guess that name, so
overlays=byod: on a PMTiles id is a 400. GET /byod/{id} returns the name, kind, and size,
not the layer names inside the archive. A tile outside the archive's zoom range, or a tile
with no bytes, is 204 with no body.
What this is not
- A place to query, join, or restyle the attributes in your file.
- A billed add-on. Usage is metered as
byod. - A replacement for the public layer catalog.
Next steps
- Layers for the catalog overlays you get without uploading anything.
- Maps API for how an overlay composes onto a style.
- Plans & Limits for how each of these requests counts as a call.