API Reference

Memory API

Store and query the memory graph — typed entities, directed relationships, and temporal facts — through the SDK Memory facade. REST details are included as a contract reference for debugging and advanced integrations.

SDK methods

Most applications should call the SDK. The memory graph is exposed on the Memory facade (constructed with an entity_id), not on the raw client — see Memory graph for usage and per-language examples.

OperationPythonTypeScript.NETGo
Create or update an entityupsert_entityupsertEntityUpsertEntityAsyncUpsertEntity
Fetch one entity by idget_entitygetEntityGetEntityAsyncGetEntity
List entitieslist_entitieslistEntitiesListEntitiesAsyncListEntities
Create or update a relationshiprelaterelateRelateAsyncRelate
List relationshipslist_relationshipslistRelationshipsListRelationshipsAsyncListRelationships
Assert a factremember_factrememberFactRememberFactAsyncRememberFact
List factslist_factslistFactsListFactsAsyncListFacts
Fact historyfact_historyfactHistoryFactHistoryAsyncFactHistory
ConsolidateconsolidateconsolidateConsolidateAsyncConsolidate

Python also ships the same methods on AsyncMemory.

Scoping

Every memory-graph request is scoped to the triple (tenant, partition, entity_id) — the same hard boundary document routes use:

  • Tenant is derived from your Bearer key; it is never client-set.
  • entity_id — the memory owner (a user, customer, patient, or agent session) — is a required query parameter on every route. The SDKs send the Memory's constructor entity_id for you.
  • partition is an optional query parameter, injected automatically when the Memory wraps a partition-scoped client. Multi-tenant keys must supply it, exactly as on document routes.

Note the two id namespaces: entity_id names the owner of the graph; memory_entity_id names a typed node inside that owner's graph.

REST contract

The SDKs call these routes internally. Use them directly only for debugging, custom clients, or advanced integrations that cannot use an SDK. Routes are versioned under the /v1 prefix; unversioned paths are deprecated aliases — see API versioning.

MethodPathPurpose
POST/v1/memory/entitiesCreate or update a typed entity node.
GET/v1/memory/entitiesList the owner's entity nodes.
GET/v1/memory/entities/{id}Fetch one entity node by id.
POST/v1/memory/relationshipsCreate or update a directed, typed edge.
GET/v1/memory/relationshipsList edges, with active/as-of filtering.
POST/v1/memory/factsAssert a temporal fact.
GET/v1/memory/factsList active facts, a point-in-time view, or a predicate's history.
POST/v1/memory/consolidateCompact the scope's active facts into a smaller set.

Common query parameters, accepted by every route above:

Query parameterTypeRequiredNotes
entity_idstringyesThe memory owner. Non-empty, at most 256 characters.
partitionstringnoPartition scope. Required for multi-tenant keys; see Multi-tenant patterns.

All timestamps are RFC 3339 strings, both in requests (valid_from, observed_at, as_of) and responses (created_at, updated_at, observed_at, invalid_from). A malformed timestamp returns 400.

Scalar values only

Entity and relationship attributes values and a fact's value must be scalar JSON — a string, number, boolean, or null. Nested objects and arrays are rejected with 400.

POST /v1/memory/entities

Creates a new entity node, or updates an existing one when memory_entity_id is supplied. Returns 201 with the entity.

Body fieldTypeRequiredNotes
entity_typestringyesCaller-controlled type (person, project, preference, …).
memory_entity_idstringnoExisting id to update, or an idempotency key. Omit to mint a new node.
display_namestringnoOptional label.
aliasesstring[]noAlternate names.
attributesobjectnoScalar attribute map ({string: string|number|bool|null}).
curlbash
curl -X POST "https://api.aetherdb.ai/v1/memory/entities?entity_id=user-42" \
  -H "Authorization: Bearer $AETHER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"entity_type": "person", "display_name": "Jane", "attributes": {"team": "billing"}}'

Response (201):

JSON
{
  "memory_entity_id": "0d9f9c1e-6b0a-4d3a-9f2e-7c1a5b8e4d21",
  "entity_id": "user-42",
  "partition": null,
  "entity_type": "person",
  "display_name": "Jane",
  "aliases": [],
  "attributes": { "team": "billing" },
  "created_at": "2026-06-30T12:00:00+00:00",
  "updated_at": "2026-06-30T12:00:00+00:00"
}

GET /v1/memory/entities

Lists the owner's entity nodes. Unset filters are simply absent from the query.

Query parameterTypeRequiredNotes
entity_typestringnoReturn only nodes of this type.
limitintnoUpper bound on returned nodes.

Response (200) wraps the nodes in an envelope:

JSON
{
  "entities": [
    {
      "memory_entity_id": "0d9f9c1e-6b0a-4d3a-9f2e-7c1a5b8e4d21",
      "entity_id": "user-42",
      "partition": null,
      "entity_type": "person",
      "display_name": "Jane",
      "aliases": [],
      "attributes": { "team": "billing" },
      "created_at": "2026-06-30T12:00:00+00:00",
      "updated_at": "2026-06-30T12:00:00+00:00"
    }
  ],
  "count": 1
}

GET /v1/memory/entities/{id}

Fetches one entity node by memory_entity_id. Returns 200 with the entity, 400 for a malformed id, or 404 when no such node exists in your scope. The response shape matches POST /v1/memory/entities.

POST /v1/memory/relationships

Creates a directed, typed edge between two entity nodes, or updates an existing edge when relationship_id is supplied. Returns 201 with the relationship.

Body fieldTypeRequiredNotes
from_entity_idstringyesSource memory_entity_id. Edges are directional.
to_entity_idstringyesTarget memory_entity_id.
relationship_typestringyesCaller-controlled type (works_at, owns, prefers, …).
relationship_idstringnoExisting id to update, or an idempotency key. Omit to mint.
attributesobjectnoScalar attribute map.
valid_fromstringnoRFC 3339 — when the relationship became true, if known.

Response (201):

JSON
{
  "relationship_id": "4e2b7c9a-1f3d-4c5b-8a6e-2d9f0b1c3e57",
  "entity_id": "user-42",
  "partition": null,
  "from_entity_id": "0d9f9c1e-6b0a-4d3a-9f2e-7c1a5b8e4d21",
  "to_entity_id": "7a1b2c3d-4e5f-4671-8293-a4b5c6d7e8f9",
  "relationship_type": "works_at",
  "attributes": {},
  "valid_from": null,
  "observed_at": "2026-06-30T12:00:00+00:00",
  "invalid_from": null,
  "created_at": "2026-06-30T12:00:00+00:00",
  "updated_at": "2026-06-30T12:00:00+00:00"
}

observed_at is when Aether ingested the edge; invalid_from is null while the edge is active and set once it is retracted or superseded.

GET /v1/memory/relationships

Lists edges. By default only active edges (invalid_from unset) are returned.

Query parameterTypeRequiredNotes
from_entity_idstringnoOnly edges leaving this node.
to_entity_idstringnoOnly edges arriving at this node.
relationship_typestringnoOnly edges of this type.
include_inactiveboolnoInclude retracted/superseded edges. Defaults to false.
as_ofstringnoRFC 3339 instant — return the edges that were active at that time.
limitintnoUpper bound on returned edges.

Response (200) is an envelope of relationships plus a count, with each item shaped as in POST /v1/memory/relationships.

POST /v1/memory/facts

Asserts a temporal predicate = value fact about the owner (default), an entity node, or a relationship edge. For a single-valued predicate, a newer conflicting fact supersedes the prior active one — the old fact is kept in history, never deleted. Returns 201 with the fact.

Body fieldTypeRequiredNotes
predicatestringyesCaller-controlled (favorite_color, status, …).
valuescalaryesString, number, boolean, or null. Always present in the body, even when null.
subject_typestringyesowner, entity, or relationship.
subject_idstringnoRequired when subject_type is entity or relationship; omitted for owner.
cardinalitystringnosingle (default) or multi. multi lets several active values coexist.
valid_fromstringnoRFC 3339 — when the fact became true in the world, if known.
observed_atstringnoRFC 3339 — overrides the ingest time.
supersedes_fact_idstringnoExplicitly replace this prior active fact (with history).

Response (201):

JSON
{
  "fact_id": "9c8d7e6f-5a4b-4c2d-9e0f-a9b8c7d6e5f4",
  "entity_id": "user-42",
  "partition": null,
  "subject_type": "owner",
  "subject_id": null,
  "predicate": "favorite_color",
  "value": "green",
  "cardinality": "single",
  "valid_from": null,
  "observed_at": "2026-06-30T12:00:00+00:00",
  "invalid_from": null,
  "supersedes_fact_id": "5f4e3d2c-1b0a-4988-b766-554433221100",
  "created_at": "2026-06-30T12:00:00+00:00",
  "updated_at": "2026-06-30T12:00:00+00:00"
}

supersedes_fact_id in the response points at the fact this one replaced (here: the prior favorite_color value). On the superseded fact, invalid_from is set.

GET /v1/memory/facts

Lists facts. By default only active facts are returned.

Query parameterTypeRequiredNotes
subject_typestringnoowner, entity, or relationship.
subject_idstringnoRequired alongside subject_type=entity or relationship.
predicatestringnoOnly facts with this predicate.
include_inactiveboolnoInclude superseded/retracted facts. Defaults to false.
as_ofstringnoRFC 3339 instant — return the facts that were active at that time.
historyboolnoReturn the full assertion chain (active + superseded) for the given subject_* + predicate instead of the active set.
limitintnoUpper bound on returned facts.

The SDK fact_history methods are sugar for history=true with the required subject_type and predicate:

curlbash
curl "https://api.aetherdb.ai/v1/memory/facts?entity_id=user-42&history=true&subject_type=owner&predicate=favorite_color" \
  -H "Authorization: Bearer $AETHER_API_KEY"

Response (200) is an envelope of facts plus a count, with each item shaped as in POST /v1/memory/facts.

POST /v1/memory/consolidate

Compacts the scope's active facts: redundant facts are soft-retracted (kept in history) so the active view stays small. Takes no request body; scoped by the common query parameters. Returns 200 with a report:

JSON
{
  "active_facts_before": 12,
  "active_facts_after": 9,
  "retracted": 3
}
Response fieldTypeNotes
active_facts_beforeintActive facts in scope before consolidation.
active_facts_afterintActive facts remaining after.
retractedintRedundant facts soft-retracted (kept in history).

Errors

Memory routes use the shared API error shape:

JSON
{
  "error": "attribute/fact values must be scalar (string, number, bool, or null)",
  "code": null,
  "request_id": "req_..."
}

Common statuses are 400 for invalid input (missing entity_id, malformed timestamps, non-scalar values, an unknown subject_type or cardinality), 401 for missing or invalid authentication, 404 for an entity id that doesn't exist in your scope, 429 for rate limits, and 500 / 503 for transient server errors. See Errors for retry guidance.