API Reference
Memory API
Store text, image, and audio memories, then query the structured 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. Construct Memory with an entity_id; it scopes text, media, and graph operations to that owner. See Multimodal memory and Memory graph for per-language examples.
| Operation | Python | TypeScript | .NET | Go |
|---|---|---|---|---|
| Remember an image | remember(image=…) | remember({ image: … }) | RememberImageAsync / RememberImageFileAsync | RememberImage |
| Remember audio | remember(audio=…) | remember({ audio: … }) | RememberAudioAsync / RememberAudioFileAsync | RememberAudio |
| 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/media | Retain image/audio bytes and index a caption/transcript. |
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.
POST /v1/memory/media
Stores the original image/audio through the normal encrypted document path and indexes a bounded caption or transcript in the same vector space as text. Search therefore returns one blended result set; media hits carry modality: "image" | "audio". The SDKs handle byte loading, MIME detection, base64 encoding, and entity/partition scope — prefer the SDK guide.
Query parameters:
| Query parameter | Type | Required | Notes |
|---|---|---|---|
entity_id | string | yes | Memory owner, supplied by the Memory facade. |
partition | string | no | Injected by a partition-scoped client. |
readers | CSV string | no | Optional read-ACL principals; authorization matches document routes. |
JSON body:
| Body field | Type | Required | Notes |
|---|---|---|---|
modality | image | audio | yes | Must agree with the MIME type and magic bytes. |
data_base64 | string | yes | Standard base64 of the original media bytes. |
content_type | string | yes | Images: JPEG, PNG, WebP, GIF. Audio: MP3, MP4/M4A, WAV, WebM, Ogg, FLAC. |
filename | string | no | Display metadata only. |
caption | string | no | Image-only caller-derived text; skips automatic VLM egress. |
transcript | string | no | Audio-only caller-derived text; skips automatic transcription egress. |
tags | string[] | no | Legacy tags. |
metadata | object | no | Scalar structured metadata. |
source | string | no | Optional origin label. |
Response (201):
{
"doc_id": "1caaf0c1-3e02-4416-8f10-8575a9e288bd",
"cid": "aether:…",
"modality": "image",
"content_type": "image/png",
"derived_text": "A whiteboard shows three release milestones.",
"derived_by": "client",
"created_at": "2026-07-09T18:00:00+00:00",
"entity_id": "user-42",
"partition": null,
"metadata": { "project": "launch" }
}
If caption/transcript is absent, the node uses its operator-configured VLM or transcription route. An unavailable route returns 422; a provider failure returns 502. Tenants assigned a strict PHI-egress policy never fall back to the normal provider route. Configuration names are an operator routing control, not evidence that a provider or deployment is HIPAA compliant.
The original bytes are available to an authorized caller from GET /v1/documents/{doc_id}/download. GET /v1/documents/{doc_id}, listings, search, and SDK recall expose only modality and the indexed derived text — they never inline binary media.
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 (including MIME/magic mismatch), 401 for missing or invalid authentication, 404 for an entity id that doesn't exist in your scope, 413 for an oversized media payload or caption/transcript, 422 when safe automatic media processing is unavailable, 429 for rate limits, and 500 / 502 / 503 for transient server/provider errors. See Errors for retry guidance.