50,000 free calls a month, card-free. Get an API key →

Documentation menu
docs / formats · raw .md

File formats

The three on-disk/wire formats an integrator meets when working offline: the channel index a device polls for updates, the installed territory package on the device, and the .drive.jsonl replay corpus that drives guidance in tests and simulators. All three are plain text/JSON over standard containers (tar, zstd), inspectable with ls, tar and jq.

Channel index (index.json)

The signed root of the update channel, served byte-exact as GET /territories (with its detached signature at GET /territories/index.sig); see territories. The detached signature is base64 ed25519 over the exact served bytes, so never reformat the JSON before verifying.

Top level:

FieldTypeMeaning
format_versionintegerIndex format version, currently 1. Readers must reject versions they do not understand
generated_atRFC 3339 stringThe newest manifest created_at in the channel (a deterministic stand-in for wall-clock time, so republishing is byte-for-byte reproducible)
territoriesarrayAll published territories, sorted by id

Each entry of territories[]:

FieldTypeMeaning
idstringStable territory identifier, e.g. "uk" (lowercase ASCII letters, digits, hyphens)
display_namestringHuman-readable name from the latest manifest
latest_versionstringThe version devices should be on (greatest published version)
versionsarrayAll published versions, ascending

Each entry of versions[]:

FieldTypeMeaning
versionstringPackage version string
data_timestampRFC 3339 stringOSM snapshot timestamp of the package data
manifest_pathstringChannel-relative path of the version's manifest.json, e.g. territories/uk/2.0.0/manifest.json
total_bytesintegerSum of all layer sizes (installed size; wire blobs are zstd-compressed and smaller)
json
{
  "format_version": 1,
  "generated_at": "2026-07-13T12:00:00Z",
  "territories": [
    {
      "id": "uk",
      "display_name": "United Kingdom",
      "latest_version": "2.0.0",
      "versions": [
        {
          "version": "2.0.0",
          "data_timestamp": "2026-07-01T00:00:00Z",
          "manifest_path": "territories/uk/2.0.0/manifest.json",
          "total_bytes": 123456789
        }
      ]
    }
  ]
}

The channel directory behind the index is nothing but static files:

python
<channel-dir>/
  index.json                     # canonical JSON, deterministic
  index.json.sig                 # base64 ed25519 over the exact bytes
  territories/<id>/<version>/
    manifest.json                # the signed package manifest, verbatim
    manifest.sig
    layers/<addr>/<name>.tar.zst # content-addressed layer blobs

Each layer blob is a deterministic tar + zstd archive containing the layer file or directory under the fixed entry name data, so blob bytes are a pure function of layer content; two versions that share a layer share the blob. <addr> is the first 16 lowercase hex characters of the layer's BLAKE3 hash from the signed manifest; <name> is the layer path's final component plus .tar.zst (e.g. layers/91c0…16 hex…/uk.pmtiles.tar.zst).

Package manifest (manifest.json)

The signed description of one package version, served byte-exact at GET /territories/{id}/{version}/manifest with its detached signature at …/manifest.sig.

FieldTypeMeaning
format_versionintegerContainer format version, currently 1
territory_idstringStable identifier, e.g. "uk"
display_namestringHuman-readable name
versionstringPackage semver, bumped on every rebuild
data_timestampRFC 3339 stringOSM snapshot the package derives from
bbox[west, south, east, north]Bounding box in WGS84 degrees
layersarrayThe map layers (below)
attributionobjectODbL block: odbl (bool), osm_attribution, recreation_recipe, notices[]
created_atRFC 3339 stringBuild time

Each entry of layers[]:

FieldTypeMeaning
kindstringLayer kind, kebab-case: valhalla-tiles (Valhalla routing tile tree), pmtiles (single-file render layer), geocode-index (offline search index), poi-sidecar (optional proprietary POI database). Unknown strings are carried verbatim so new kinds need no format bump
pathstringLayer file or directory, relative to the package root, forward slashes
bytesintegerTotal size (directories: sum of file sizes)
blake3stringLowercase hex BLAKE3 of the file, or of the deterministic directory hash tree for directory layers

Installed package on disk

The device-side TerritoryStore owns a root directory (you choose it: e.g. filesDir/territories on Android, Application Support on iOS) laid out as:

python
<root>/
  state.json          # { "active": "uk" } (written atomically)
  staging/            # temp dirs for in-flight installs (same filesystem,
                      # so promotion is an atomic rename)
  territories/<id>/   # verified, installed packages
    manifest.json     # the signed manifest, verbatim
    manifest.sig
    <layer paths…>    # exactly the layers[].path entries, e.g.
                      #   valhalla/…      (valhalla-tiles tile tree)
                      #   uk.pmtiles      (render layer)
                      #   geocode/…       (search index)

An installed package directory is the unpacked .snpkg: the manifest, its signature and the layer files at their manifest-declared relative paths. A .snpkg archive itself is the same directory packed as deterministic tar + zstd. Treat the store's root as opaque in production code (resolve layer paths through TerritoryStore.layerPath(territoryId, kind) rather than hard-coding them), but the layout is stable and inspectable for debugging and backups. Installs are transactional: packages are staged inside the root, verified end to end (ed25519 over the manifest bytes, then per-layer BLAKE3), and only then atomically renamed into territories/; a package that fails verification never becomes visible.

.drive.jsonl replay corpus

The QA corpus format produced by the sn-replay harness and consumed by ReplayLocationProvider on Android and iOS (see the Android API reference): a JSON-lines file where line 1 is a header and every following non-blank line is one GPS fix. It replays bit-identically on the JVM, on device and in the Rust harness: record once, assert everywhere.

Header line:

FieldTypeRequiredMeaning
format_versionintegeryesCurrently 1; readers reject anything newer
namestringyes (non-empty)Short machine-friendly corpus name
descriptionstringyesHuman-readable drive scenario
route_fixturestringyesRelative path of the route fixture the drive replays against
seedintegeryesPRNG seed; with generator makes the corpus byte-for-byte reproducible
generatorobjectoptionalThe synthesis parameters that produced the drive (speed model, GPS noise, dropouts, …)

Fix lines:

FieldTypeRequiredMeaning
t_msintegeryesMilliseconds since drive start; strictly increasing
latnumberyesLatitude, [-90, 90]
lonnumberyesLongitude, [-180, 180]
speed_mpsnumberoptionalSpeed over ground, ≥ 0
bearing_degnumberoptionalCourse over ground, [0, 360) clockwise from true north
horizontal_accuracy_mnumberoptionalEstimated 1-sigma horizontal accuracy, > 0

Validation (enforced identically by the Rust, Kotlin and Swift parsers): supported format_version, non-empty name, at least one fix, strictly increasing t_ms, finite in-range coordinates and channels.

Sample: the opening of the committed urban-3step-clean fixture (crates/sn-replay/fixtures/drives/urban-3step-clean.drive.jsonl; the fixtures/drives/ directory holds seven ready-made corpora covering motorway, urban, roundabout, tunnel-dropout and U-turn scenarios):

json
{"format_version":1,"name":"urban-3step-clean","description":"Clean 1 Hz drive along the urban 3-step route.","route_fixture":"routes/urban-3step.route.json","seed":1001,"generator":{"preset":"clean","speed":{"kind":"constant","mps":12.0},"gps_noise_sigma_m":0.0,"bearing_noise_sigma_deg":0.0,"speed_noise_sigma_mps":0.0,"accuracy_base_m":3.0,"accuracy_jitter_m":0.0,"reacquisition_boost":1.0,"dropouts":[],"tail_fixes":3}}
{"t_ms":0,"lat":52.2,"lon":0.13,"speed_mps":12.0,"bearing_deg":90.0,"horizontal_accuracy_m":3.0}
{"t_ms":1000,"lat":52.2,"lon":0.1301761,"speed_mps":12.0,"bearing_deg":90.0,"horizontal_accuracy_m":3.0}
{"t_ms":2000,"lat":52.2,"lon":0.1303522,"speed_mps":12.0,"bearing_deg":90.0,"horizontal_accuracy_m":3.0}

Synthesising your own corpus from route geometry is legitimate: keep t_ms strictly increasing and channels in range and any conforming file replays. A generator block is optional provenance, not required for playback.

Next steps

  • Territories: the trust model and endpoints these formats travel over
  • Android API reference: TerritoryStore, DriveCorpus, ReplayLocationProvider
  • SDKs: install coordinates and the verifying-key trust anchor