openapi: 3.2.0 info: description: emem is shared memory for AI agents working together in the real world. license: name: Apache-2.0 title: emem Knowledge Graph API version: 2.4.0 x-emem-surface-asymmetry: memory_notes: MCP only reach_them_at: POST /mcp, method tools/call read_side_is_here: - /v1/memory/search - /v1/memory/sse - /memories/{path} tools: - emem_memory_create - emem_memory_view - emem_memory_delete - emem_memory_rename - emem_memory_str_replace - emem_memory_supersede why_not_here: These write the agent correspondence plane, which is prose and untrusted-by-declaration. It is deliberately not part of the REST fact surface, and the two planes are kept apart rather than merged for convenience. servers: - description: Hosted instance (HTTPS-only) url: https://emem.dev tags: - name: knowledge-graph paths: /v1/edges: post: description: 'Persist temporal knowledge-graph edges. Body is a signed Attestation envelope whose `edges[]` array carries each edge {subj, pred, obj, valid_from, valid_to?, confidence, signer, signed_at, schema_cid?, note?}. The edge leaves are folded into the merkle root so the signature commits to them. Additive: an attestation with no edges behaves exactly as /v1/attest.' operationId: emem_edges_write requestBody: content: application/json: schema: properties: edges: items: properties: confidence: type: number note: type: string obj: description: object fact CID type: string pred: type: string subj: description: subject fact CID type: string valid_from: type: integer valid_to: type: integer required: - subj - pred - obj - valid_from - confidence - signer - signed_at type: object type: array required: - facts - edges - batch_root - attester - signature type: object required: true responses: '200': content: application/json: schema: type: object description: ok default: content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' description: 'error, the emem.error.v1 envelope. Branch on the stable `code` (see GET /v1/errors), not the message. A malformed or missing-field request body returns `code: invalid_argument` with the offending field named in `message`.' summary: Persist temporal knowledge-graph edges. tags: - knowledge-graph /v1/edges/recall: post: description: 'Read temporal knowledge-graph edges (subj --pred--> obj, valid over [valid_from, valid_to)), bi-temporally filtered, in EITHER direction. Forward (`subj`, direction="out", the default): edges originating at a subject fact. Reverse (`obj`, direction="in"): edges pointing AT a fact, what disagrees-with / supersedes / relates-to it. Returns a signed list of edges plus the distinct neighbour fact CIDs (`objs` for out, `subjs` for in); the receipt commits the returned edge CIDs into its signature preimage. When to use: Call this to read the typed CONNECTIONS of a fact, what disagrees with it, what superseded it, what relates to it, as of a point in time. A plain recall gives you the fact; this gives you how that fact links to others in the memory graph. Ask it when the user says ''what is this related to'', ''what replaced this observation'', ''why is this value contested'', or ''what did this place''s relations look like as of date X''. Pick a direction: set `subj` (direction="out") to ask ''what does this fact point at''; set `obj` (direction="in") to ask the REVERSE, ''what disagrees-with / supersedes / points-at this fact''. Set exactly one of subj/obj, an ambiguous or empty request errors honestly rather than returning a silent empty. Pass `as_of_tslot` to get the latest edge per neighbour whose valid interval covers that moment (newer edges shadow older, nothing is deleted); pass `pred` (e.g. `disagrees_with`, `supersedes`) to filter, or omit it (empty string) for every predicate. Tip: a quicker way to get a fact + its outbound edges in one shot is `emem_recall` with include:["edges"]. Follow each edge''s `obj`/`subj` with `emem_fetch` to resolve the related fact, or `emem_verify_receipt` to confirm the signature offline.' operationId: emem_edges_recall requestBody: content: application/json: schema: properties: as_of_tslot: description: valid-time bound; latest edge per neighbour whose interval covers it type: integer direction: description: out (default)=subj→objs; in=obj→subjs; inferred from which of subj/obj is set when omitted enum: - out - in type: string limit: default: 100 maximum: 1000 minimum: 1 type: integer obj: description: 'object fact CID (reverse / direction=in): what points at this fact' type: string pred: description: predicate filter; empty string scans all predicates type: string subj: description: subject fact CID (forward / direction=out); set exactly one of subj/obj type: string type: object required: true responses: '200': content: application/json: schema: $ref: '#/components/schemas/SignedResponse' description: ok default: content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' description: 'error, the emem.error.v1 envelope. Branch on the stable `code` (see GET /v1/errors), not the message. A malformed or missing-field request body returns `code: invalid_argument` with the offending field named in `message`.' summary: Recall temporal knowledge-graph edges in either direction, bi-temporally… tags: - knowledge-graph components: schemas: Cost: description: 'Self-declared cost block on every receipt. Honest accounting: latencies are observed, freshness is the age of the stalest source cited (null when undatable, never 0 as a stand-in), `was_cached` is true when the hot cache served the read.' properties: credits: description: Conceptual cost units; 0 for L0/L1 read endpoints on the hosted responder. type: number latency_p50_ms: type: number latency_p99_ms: type: number source_freshness_s: description: 'Age of the STALEST source this response cites: now minus the earliest captured_at across the returned facts'' sources. null when nothing in the response carries a dated source, which is the honest answer for a primitive that reads no observation. Was a hardcoded 0 until 2026-08-05, so a 2021 DEM tile reported as 0 s old; a null here means unknown, never fresh.' type: - integer - 'null' was_cached: type: boolean type: object Fact: description: A primary attestation at (cell, band, tslot). `value` is the band's typed reading (number, array of numbers for vector bands, or a categorical class id). `unit` is the band's declared unit (e.g. `m_msl`, `degC`, `mm`). properties: absence_reason: description: Present only when kind=`absence`. enum: - unavailable_capability - outside_coverage - archetype_seed_unavailable - gpu_unavailable - upstream_error - upstream_timeout type: string band: type: string cell: $ref: '#/components/schemas/Cell64' fact_cid: $ref: '#/components/schemas/FactCid' kind: description: '`primary` = signed measurement; `absence` = signed "we don''t have this here" with a typed reason.' enum: - primary - absence type: string provenance: description: Upstream source key (e.g. `copdem30m`, `s2_l2a`, `cams_eu`). type: string receipt: $ref: '#/components/schemas/Receipt' tslot: $ref: '#/components/schemas/Tslot' unit: type: string value: description: Number, array of numbers, or class id depending on band type. required: - kind - cell - band - tslot - value - fact_cid - receipt type: object MaterializeNote: description: 'One entry in the response''s `materialize_notes[]`, recording what the lazy materializer did during this call. status:"materialized" means a signed fact was minted and persisted (a Primary observation OR a confirmed, evidence-backed Absence - both are signed and citeable by fact_cid). status:"skipped" means nothing was signed: `reason_class` says why (transient `timeout`/`upstream_error`, retryable; or structural `unknown_band`/`no_materializer`/`capability_unavailable`, not retryable here) and `absence` is always false, because a skip is ''unknown'', never a confirmed absence.' properties: absence: description: Always false on a skip; a confirmed absence is a signed fact with status:materialized, not a skip. type: boolean band: type: string cell: $ref: '#/components/schemas/Cell64' fact_cid: type: string latency_ms: type: number ok: type: boolean reason: type: string reason_class: enum: - timeout - upstream_error - unknown_band - no_materializer - capability_unavailable type: string retryable: type: boolean status: enum: - materialized - skipped type: string type: object ErrorEnvelope: description: The `emem.error.v1` failure envelope returned by every endpoint on a 4xx/5xx. Branch on the stable `code` (not the human `message`). See GET /v1/errors for the full code catalog. properties: code: description: Stable machine-readable error code. One of the codes in GET /v1/errors. example: invalid_argument type: string details: description: Optional structured recovery hints; present on errors that ship machine-readable next-steps. type: object message: description: Human-readable detail. For invalid_argument this names the offending field (e.g. "missing field `q`"). type: string path: description: Request path that produced the error. example: /v1/ask type: string schema: const: emem.error.v1 type: string required: - code - message - schema type: object PubKey: description: Ed25519 32-byte public key, base32-nopad-lowercase encoded (52 chars). Returned in receipts and `/.well-known/emem.json`. example: 777er3yihgifqmv5hmc2wwmyszgddzderzhsx6rex4yoakwomvka type: string Cell64: description: 'cell64 wire form: four base-65,536 bigrams separated by dots, e.g. `defi.zb4d9.pefa.zf619`. Encoded resolution is ~9.55 m at the equator. Each bigram is either a CVCV quad, consonant `[bcdfghjklmnpqrstvwxyz]` followed by vowel `[aeiouAEIOU]` repeated twice, OR a synthetic 5-char `z[0-9a-f]{4}` slot used for the unused pad cells in the 65,536-entry alphabet. The regex pin matches `pattern` below byte-for-byte and is also surfaced under `Cell64Pattern` so agents can validate before sending.' example: defi.zb4d9.pefa.zf619 maxLength: 23 minLength: 19 pattern: ^(?:(?:[bcdfghjklmnpqrstvwxyz][aeiouAEIOU]){2}|z[0-9a-f]{4})(?:\.(?:(?:[bcdfghjklmnpqrstvwxyz][aeiouAEIOU]){2}|z[0-9a-f]{4})){3}$ type: string SignedResponse: description: Standard recall envelope. `facts` is the array of signed facts touched by this call (subset of `bands_already_attested_at_cell` after auto-materialization). `receipt` is the responder's signature over the call. `materialize_notes` lists any lazy-materializer activity that happened to satisfy the request, empty for purely warm reads. properties: bands_already_attested_at_cell: description: Bands the cell already has facts for, regardless of whether they were requested. Useful for follow-up calls without a second /v1/coverage_matrix hit. items: type: string type: array caveats: description: Plain-language constraints the caller should fold into their answer (grid resolution, revisit cadence, sample-size warnings). items: type: string type: array facts: items: $ref: '#/components/schemas/Fact' type: array materialize_notes: items: $ref: '#/components/schemas/MaterializeNote' type: array receipt: $ref: '#/components/schemas/Receipt' required: - facts - receipt type: object FactCid: description: 'Content id of a fact: base32-nopad-lowercase encoding of `blake3(canonical_cbor(fact))`, the FULL 32-byte digest with no truncation. Always 52 characters, alphabet `[a-z2-7]`. A cid of any other length is a damaged citation, not a shorter address: /v1/memory_token/resolve rejects it as `fact_cid_malformed_length` rather than guessing. Note that `entity_cid` and `bundle_cid` are NOT this shape; both truncate to 16 bytes (26 characters) and hash an identity anchor or a citation list rather than a complete body.' example: qtv2bco56qw4pmlohk56dotoxyl3atmnjpmzrijj2kazw2mj57oq maxLength: 52 minLength: 52 pattern: ^[a-z2-7]{52}$ type: string Tslot: description: Band-tempo-relative integer offset from the emem epoch. Each band declares its tempo (`fast` / `medium` / `slow` / `static`); tslot is the rounded count of that tempo's unit since the epoch. minimum: 0 type: integer Receipt: description: 'Ed25519-signed receipt. The browser-side verifier at /verify reconstructs the preimage from the receipt fields alone, no callback to the issuer. **A receipt is byte-for-byte or nothing.** Current receipts carry `preimage_version: 2`, whose preimage binds request_id, served_at, primitive, cells, fact_cids AND, when present, the scope / as_of / edges / source_versions / field digests and the `merkle_proof` segment. Reshaping a receipt — dropping a field an SDK considers redundant, re-keying it, summarising it, round-tripping it through a lossy model — invalidates the signature BY DESIGN, and the result is indistinguishable on the wire from tampering. Store and forward the responder''s exact bytes. POST /v1/verify_receipt names which of the two it is where it can prove the difference (`reason: receipt_reshaped_after_signing` with a `failure_detail`). What is NOT signed: the caller''s `place`/`q` string, raw `lat`/`lng`, requested `bands[]`, requested `tslot`, and `intent` — a wrong-place geocode produces a valid signature for the wrong cell. Branch on /v1/locate `selected.is_high_confidence` before trusting place-anchored answers. Also: `fact_cid` is per-replica (signed_at differs across responders even for byte-identical upstream pixels); cross-replica join key is the tuple (cell, band, tslot). /v1/recall_polygon emits one independently signed receipt per cell under `by_cell..receipt`, `merged_facts[]` is convenience flattening and is NOT covered by an aggregate signature.' properties: cells: items: $ref: '#/components/schemas/Cell64' type: array cost: $ref: '#/components/schemas/Cost' fact_cids: items: $ref: '#/components/schemas/FactCid' type: array intent: description: Optional natural-language hint. Populated when served via /v1/intent. type: string merkle_proof: description: 'Inclusion proof for `fact_cids[0]` when persisted. Omitted from JSON when the cited facts pre-date the proof tree; under preimage_version 2 that absence is itself signed (an explicit ABSENT marker), so it is a statement rather than a gap. Do not strip this field: v2 binds it into the signature and removing it makes an authentic receipt report `signature_valid: false`.' properties: leaf_index: description: u32 leaf index in the canonical-sorted batch. type: integer path: description: Sibling hashes leaf→root. items: description: 32-byte sibling hash as a byte array items: type: integer type: array type: array root: description: The expected 32-byte batch root as a byte array. items: type: integer type: array version: description: 'Merkle hashing rule: 0 (omitted) = legacy unprefixed, 1 = RFC 6962-style prefixed.' type: integer required: - leaf_index - path - root type: object primitive: description: 'Namespaced wire form: `emem.recall`, `emem.find_similar`, `emem.verify`, …' type: string registry_cid: description: CID of the function registry version in force. type: string request_id: description: ULID generated per request. type: string responder: $ref: '#/components/schemas/PubKey' responder_key_epoch: description: u32 rotation counter; bumps when the operator rotates keys. type: integer responder_pubkey_b32: $ref: '#/components/schemas/PubKey' schema_cid: description: CID of the active CDDL profile. type: string served_at: description: ISO 8601 UTC, second precision. type: string signature: description: Ed25519 signature, 64 bytes base32-nopad-lowercase encoded. type: string source_versions: additionalProperties: type: string description: Per-source freshness map. type: object required: - request_id - served_at - primitive - cells - fact_cids - schema_cid - responder - responder_key_epoch - responder_pubkey_b32 - signature - registry_cid type: object