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.
| Operation | Python | TypeScript | .NET | Go |
|---|---|---|---|---|
| Create or update an entity | upsert_entity | upsertEntity | UpsertEntityAsync | UpsertEntity |
| Fetch one entity by id | get_entity | getEntity | GetEntityAsync | GetEntity |
| List entities | list_entities | listEntities | ListEntitiesAsync | ListEntities |
| Create or update a relationship | relate | relate | RelateAsync | Relate |
| List relationships | list_relationships | listRelationships | ListRelationshipsAsync | ListRelationships |
| Assert a fact | remember_fact | rememberFact | RememberFactAsync | RememberFact |
| List facts | list_facts | listFacts | ListFactsAsync | ListFacts |
| Fact history | fact_history | factHistory | FactHistoryAsync | FactHistory |
| Consolidate | consolidate | consolidate | ConsolidateAsync | Consolidate |
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 theMemory's constructorentity_idfor you.partitionis an optional query parameter, injected automatically when theMemorywraps 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.
| Method | Path | Purpose |
|---|---|---|
POST | /v1/memory/entities | Create or update a typed entity node. |
GET | /v1/memory/entities | List the owner's entity nodes. |
GET | /v1/memory/entities/{id} | Fetch one entity node by id. |
POST | /v1/memory/relationships | Create or update a directed, typed edge. |
GET | /v1/memory/relationships | List edges, with active/as-of filtering. |
POST | /v1/memory/facts | Assert a temporal fact. |
GET | /v1/memory/facts | List active facts, a point-in-time view, or a predicate's history. |
POST | /v1/memory/consolidate | Compact the scope's active facts into a smaller set. |
Common query parameters, accepted by every route above:
| Query parameter | Type | Required | Notes |
|---|---|---|---|
entity_id | string | yes | The memory owner. Non-empty, at most 256 characters. |
partition | string | no | Partition 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 field | Type | Required | Notes |
|---|---|---|---|
entity_type | string | yes | Caller-controlled type (person, project, preference, …). |
memory_entity_id | string | no | Existing id to update, or an idempotency key. Omit to mint a new node. |
display_name | string | no | Optional label. |
aliases | string[] | no | Alternate names. |
attributes | object | no | Scalar attribute map ({string: string|number|bool|null}). |
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):
{
"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 parameter | Type | Required | Notes |
|---|---|---|---|
entity_type | string | no | Return only nodes of this type. |
limit | int | no | Upper bound on returned nodes. |
Response (200) wraps the nodes in an envelope:
{
"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 field | Type | Required | Notes |
|---|---|---|---|
from_entity_id | string | yes | Source memory_entity_id. Edges are directional. |
to_entity_id | string | yes | Target memory_entity_id. |
relationship_type | string | yes | Caller-controlled type (works_at, owns, prefers, …). |
relationship_id | string | no | Existing id to update, or an idempotency key. Omit to mint. |
attributes | object | no | Scalar attribute map. |
valid_from | string | no | RFC 3339 — when the relationship became true, if known. |
Response (201):
{
"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 parameter | Type | Required | Notes |
|---|---|---|---|
from_entity_id | string | no | Only edges leaving this node. |
to_entity_id | string | no | Only edges arriving at this node. |
relationship_type | string | no | Only edges of this type. |
include_inactive | bool | no | Include retracted/superseded edges. Defaults to false. |
as_of | string | no | RFC 3339 instant — return the edges that were active at that time. |
limit | int | no | Upper 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 field | Type | Required | Notes |
|---|---|---|---|
predicate | string | yes | Caller-controlled (favorite_color, status, …). |
value | scalar | yes | String, number, boolean, or null. Always present in the body, even when null. |
subject_type | string | yes | owner, entity, or relationship. |
subject_id | string | no | Required when subject_type is entity or relationship; omitted for owner. |
cardinality | string | no | single (default) or multi. multi lets several active values coexist. |
valid_from | string | no | RFC 3339 — when the fact became true in the world, if known. |
observed_at | string | no | RFC 3339 — overrides the ingest time. |
supersedes_fact_id | string | no | Explicitly replace this prior active fact (with history). |
Response (201):
{
"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 parameter | Type | Required | Notes |
|---|---|---|---|
subject_type | string | no | owner, entity, or relationship. |
subject_id | string | no | Required alongside subject_type=entity or relationship. |
predicate | string | no | Only facts with this predicate. |
include_inactive | bool | no | Include superseded/retracted facts. Defaults to false. |
as_of | string | no | RFC 3339 instant — return the facts that were active at that time. |
history | bool | no | Return the full assertion chain (active + superseded) for the given subject_* + predicate instead of the active set. |
limit | int | no | Upper bound on returned facts. |
The SDK fact_history methods are sugar for history=true with the required subject_type and predicate:
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:
{
"active_facts_before": 12,
"active_facts_after": 9,
"retracted": 3
}
| Response field | Type | Notes |
|---|---|---|
active_facts_before | int | Active facts in scope before consolidation. |
active_facts_after | int | Active facts remaining after. |
retracted | int | Redundant facts soft-retracted (kept in history). |
Errors
Memory routes use the shared API error shape:
{
"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.