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.

OperationPythonTypeScript.NETGo
Remember an imageremember(image=…)remember({ image: … })RememberImageAsync / RememberImageFileAsyncRememberImage
Remember audioremember(audio=…)remember({ audio: … })RememberAudioAsync / RememberAudioFileAsyncRememberAudio
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/mediaRetain image/audio bytes and index a caption/transcript.
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.

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 parameterTypeRequiredNotes
entity_idstringyesMemory owner, supplied by the Memory facade.
partitionstringnoInjected by a partition-scoped client.
readersCSV stringnoOptional read-ACL principals; authorization matches document routes.

JSON body:

Body fieldTypeRequiredNotes
modalityimage | audioyesMust agree with the MIME type and magic bytes.
data_base64stringyesStandard base64 of the original media bytes.
content_typestringyesImages: JPEG, PNG, WebP, GIF. Audio: MP3, MP4/M4A, WAV, WebM, Ogg, FLAC.
filenamestringnoDisplay metadata only.
captionstringnoImage-only caller-derived text; skips automatic VLM egress.
transcriptstringnoAudio-only caller-derived text; skips automatic transcription egress.
tagsstring[]noLegacy tags.
metadataobjectnoScalar structured metadata.
sourcestringnoOptional origin label.

Response (201):

JSON
{
  "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 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 (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.