Skip to main content

The protocol · API v1

Read it once, wire it once.

What you need to register a sensor, call recall, stand up an alert loop, and verify a receipt offline. Every answer the API returns carries an ed25519 signature in the same shape as the open emem.dev ledger. One verifier checks public and private memory alike.

basehttps://api.geo.qa/v1
mcphttps://mcp.geo.qa
authBearer $GEOQA_KEY
keys/.well-known/emem.json
updated2026-05-28 · v1.6
receipts: ed25519 · BLAKE3 · canonical CBOR · verifiable offline

§ 1

Quickstart

Four steps. Get a token, register one sensor, ask recall for the last hour, and read the receipt that comes back. The same four calls scale from one camera to a fleet.

1 · Get a token

Create a tenant at geo.qa, then mint a key under Settings → Keys. The secret is shown once. A tenant key writes and reads; for agents, issue a scoped recall-only key instead (see § 3).

2 · Register a sensor

POST /v1/sensors
curl -X POST https://api.geo.qa/v1/sensors \
  -H "Authorization: Bearer $GEOQA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "kind": "camera",
    "name": "south-yard",
    "uri": "rtsp://192.0.2.10/stream",
    "place": { "lat": 27.485, "lng": 51.531 }
  }'

The video pipeline accepts RTSP, RTMP, WebRTC, and HLS. geo.qa resolves the place to its ground cell, encodes each frame to a latent on a custom capture device, and files it against that cell on the UltraFast (hourly) tempo tier. The raw frame stays on the device; only the latent lands in your memory.

3 · Ask a question

POST /v1/recall
curl -X POST https://api.geo.qa/v1/recall \
  -H "Authorization: Bearer $GEOQA_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "place": "south-yard", "window": "PT1H" }'

4 · Read the receipt

Every response carries a top-level receipt block. The hash is BLAKE3 over the canonical-CBOR payload; the signature is ed25519 over that hash. Nothing about it depends on us being online.

# the receipt block on every /v1/recall response
{
  "facts": [ /* fused, dated observations */ ],
  "receipt": {
    "cid": "bafy2bz…q7m4",        // base32 of BLAKE3, 26 chars
    "hash": "blake3:71d40a90…",
    "signer": "org_acme.ops",
    "sig": "ed25519:9f3c1a…"     // 64 bytes
  }
}

Verify it with the CLI or any ed25519 library. See § 10.

§ 2

Concepts

The whole API is one algebra: (cell, band, tslot) → Fact. Where, what, when, resolves to a value you can quote. Learn these five nouns and the rest is shape.

nounwhat it is
cellWhere. An H3-resolved ground address, roughly 10 m across. Every place resolves to exactly one cell; an observation from any sensor files against it. Public emem facts use the open cell64 grid (~9.55 m); both cite the same way.
bandWhat. A named measurement slot. indices.ndvi, weather.temperature_2m, surface_water.recurrence, a camera latent, a signed report. 124 bands are wired today; 42 are callable by name.
tslotWhen. A temporal slot, unix / slot. Tempo tiers run Static, Slow (365 d), Medium (30 d), Fast (1 d), UltraFast (1 h). History is append-only; reads are bi-temporal, so the past stays queryable.
factThe answer. A value at one cell × band × tslot with its provenance. PrimaryFact is a direct measurement. DerivativeFact is an operation over parents (delta, mean, trend, rate, anomaly). NegativeFact is a signed absence with a reason: outside_coverage, gpu_unavailable. No silent fallbacks. The API tells you when a place was never queried, not just when it came back empty.
receiptThe proof. Canonical CBOR → BLAKE3 hash → 16-byte CID (base32, 26 chars) → ed25519 signature. A batch rolls up to a BLAKE3 Merkle root. Verifies offline against the signer’s public key.

§ 3

Authentication

Bearer tokens, scoped to a tenant. Rotate them from the dashboard or over the API. A key never leaves your organisation, and writes are capability-bound: facts land under /memories/by_attester/<pubkey>/ and carry the signer’s public key.

Authorization: Bearer $GEOQA_KEY

Agent keys

For agents, issue a separate agent key. Agent keys are recall-only by default and can be pinned to a polygon, a time window, and a list of sensors: the same scope a tenant key would have, minus the right to write or to read outside the fence. This is what you hand an MCP client.

# mint a recall-only agent key, scoped to one corridor
curl -X POST https://api.geo.qa/v1/keys \
  -H "Authorization: Bearer $GEOQA_KEY" \
  -d '{
    "role": "recall",
    "scope": {
      "polygon": "corridor-04A",
      "window": "P30D",
      "sensors": ["cam.18", "cam.21"]
    }
  }'

§ 4

Recall

The endpoint your agents hit most. Give it a place and a window; it returns the dated facts that ground an answer, each tagged with the modality and source it came from, plus one receipt over the whole set.

POST
https://api.geo.qa/v1/recall

Request

{
  "place": "south-yard",            // name, GeoJSON polygon, or cell id
  "window": "PT1H",                  // ISO-8601 duration or interval
  "bands": ["indices.ndvi", "sar.vv"], // optional band filter
  "k": 12,                           // top-k facts, default 12
  "include_public": true             // also cite the emem.dev ledger
}

Response

{
  "facts": [
    {
      "cell":     "8a1f0b27a4dffff",
      "band":     "flood_extent",
      "tslot":    "2026-05-04T03:14:09Z",
      "type":     "primary",
      "value":    0.0,
      "modality": "sar",
      "source":   "sentinel-1"
    }
    /* … 3 more, 2 modalities … */
  ],
  "receipt": { "cid": "bafy2bz…q7m4", "signer": "org_acme.ops", "sig": "ed25519:…" }
}

Pass place as a GeoJSON polygon to read a whole catchment or corridor in one call, the same operation as emem_recall_polygon on the MCP surface.

§ 5

Sensors

A sensor is anything that writes to your memory: a camera stream, a drone tasking, a satellite product subscription, a webhook of field reports.

POST
/v1/sensors. Register a sensor and bind it to a place.
GET
/v1/sensors. List sensors, with health and last-seen tslot.
PATCH
/v1/sensors/:id. Update geometry, schedule, or place.
DELETE
/v1/sensors/:id. Soft-delete; the facts already filed stay in the append-only log.

§ 6

Alerts & loops

A loop is a saved query that runs on a cadence. When its condition holds, geo.qa emits an alert and attaches a receipt for the facts that fired it. The alert is auditable weeks later, not just a notification you have to trust.

POST
/v1/loops. Create a monitoring loop.
# watch a corridor for SAR-derived vegetation loss, daily
{
  "name":      "corridor-04A-veg",
  "place":     "corridor-04A",
  "condition": "sar_delta > 3.0",    // a DerivativeFact threshold
  "cadence":   "PT24H",
  "notify":    ["webhook:my-pager", "mailto:ops@acme.ops"]
}

Conditions read like band algebra. Built-in event hunters cover deforestation, wildfire, flood extent, and algal bloom, and map to the emem_hunt tool on the MCP surface.

§ 7

Webhooks

Every payload is signed in the X-Geoqa-Signature: ed25519=… header over the raw body. Verify it before you act on it. Delivery retries with exponential backoff for up to 24 hours; failed attempts are visible in the dashboard, and the body is identical on every retry so the signature stays valid.

// POST to your endpoint, headers then body
X-Geoqa-Signature: ed25519=9af2…be21
X-Geoqa-Delivery:  d_0d1f… (stable across retries)

{
  "event":    "alert.fired",        // alert.fired · change.detected · memory.created
  "loop":     "corridor-04A-veg",
  "tslot":    "2026-05-04T03:14:09Z",
  "facts":    [ /* the observations that fired it */ ],
  "receipt":  { "cid": "bafy2bz…", "sig": "ed25519:…" }
}

§ 8

MCP server

Every tenant is also a Model Context Protocol server. Point your agent at https://mcp.geo.qa and authenticate with a recall-only agent key (§ 3). Read tools carry readOnlyHint; the open ledger at emem.dev is exposed as cite_public, so an agent reads your private memory and the public commons in one answer.

The tool surface

The 81-tool surface speaks the open emem protocol. The core reads, domain shortcuts, and physics solvers you will reach for most:

emem_locatea place → its stable cell address
emem_askfree-text geo question → a cited answer
emem_recallevery signed fact filed at a cell
emem_recall_polygonread a whole region in one call
emem_find_similark-NN over the 128-D GeoTessera embedding
emem_verifyevaluate a claim against the memory
emem_verify_receiptoffline ed25519 + BLAKE3 signature check
emem_ndvi · air · lstdomain shortcuts for the common bands
emem_water · forest · weathermore domain shortcuts, same receipt shape
emem_heat_solve · wave_solvephysics solvers over the state cube
emem_jepa_predictnext-latent prediction (head untrained today)
cite_publicresolve a fact from the open emem.dev ledger

Client config

Drop this into your MCP client (Claude Desktop, Cursor, any agent runtime). The key is a scoped agent key, not your tenant key.

mcp.json
{
  "mcpServers": {
    "geoqa": {
      "url": "https://mcp.geo.qa",
      "headers": {
        "Authorization": "Bearer $GEOQA_AGENT_KEY"
      }
    }
  }
}

§ 9

SDKs

First-party clients for Python and TypeScript. Both are thin: they wrap the REST API, handle retries, and verify every receipt before handing you the facts.

pip install geoqa
npm install @geoqa/client
recall.py
# the friendly verbs map onto the same protocol
import os
from geoqa import Client

g = Client(key=os.environ["GEOQA_KEY"])

cell = g.locate(lat=27.485, lon=51.531)
ans  = g.recall(place="south-yard", window="PT1H")

assert ans.verify()        # offline ed25519 check
print(ans.facts, ans.receipt.cid)

The TypeScript client mirrors it: await g.recall({ place, window }), then ans.verify(). Both also expose ask(), watch(), and verify() over the same endpoints.

§ 10

Verify a receipt

Verification needs no account and no network. Every receipt checks out from the receipt plus the signer’s public key, with no callback to geo.qa. Canonical CBOR makes the payload byte-deterministic, BLAKE3 hashes it to 32 bytes, and ed25519 signs the hash. A batch verifies against its BLAKE3 Merkle root, so you can check ten thousand facts against one signature.

verify
# the public key lives at a well-known path
geoqa verify receipt.cbor \
  --pubkey https://geo.qa/.well-known/emem.json

→ ok · signer org_acme.ops · blake3 71d40a90… · ed25519 valid

The format is identical to emem.dev, so a single verifier handles your private receipts and the public ledger. On the MCP surface this is emem_verify_receipt.

§ 11

Errors

Standard HTTP status codes with a machine-readable code and a human message in the body.

statusmeaning
400request shape or band grammar error; fix and resend
401missing, malformed, or expired key
403key is valid but lacks scope for this place, window, or sensor
404no such sensor, loop, or cell in your tenancy
409resource already exists with a different shape
429rate-limited; back off per Retry-After
5xxour side; the request is safe to retry

A NegativeFact is not an error. If a place was outside coverage or a GPU was unavailable, you get a signed 200 with type: "negative" and a reason, not a 404.

§ 12

Rate limits

Daily caps follow your plan on the pricing page, with a hard burst ceiling on top. Every response carries X-RateLimit-Remaining and X-RateLimit-Reset; we email at 80% and 95% of a daily cap. On 429, honour Retry-After. The SDKs do this for you.

tierburst ceilingrecall window
Free6 req / s / orgtop-k 12
Team60 req / s / orgtop-k 64
Enterprisenegotiatedunbounded in-tenancy

§ 13

Changelog

v1.6 · 2026-05NegativeFact reasons exposed on every read; bands widened to 124 wired (42 callable); MCP surface at 81 tools.
v1.5 · 2026-02Physics solvers on the MCP surface (heat_solve, wave_solve, jepa_predict); polygon recall.
v1.4 · 2025-11GeoTessera 128-D embeddings wired to find_similar; bi-temporal reads.
v1.3 · 2025-08MCP server, public emem.dev wiring as cite_public, signed-receipt CLI verifier.
v1.2 · 2025-05Alerts & monitoring loops, webhook delivery with retry.
v1.0 · 2025-02First public release: recall, sensors, signed receipts.

Wire one sensor today.

Mint a key, register a stream, and read your first signed receipt before your coffee’s cold. We’ll set up the tenancy with you.

geo.qa · a vortx ground decoder · emem.dev open protocol