Memories API
Query and manage a character's memory of a specific player. Estuary automatically builds memory from conversations -- this API lets you read what the character remembers, search across it, visualize it as a knowledge graph, and edit it manually.
Base path: /api/v1/characters/{character_id}/players/{player_id}/memories
:::info character_id
The character_id in the URL path is the id returned by the Characters API.
:::
Query parameter names are camelCase, and unknown query parameters are ignored, so a snake_case name silently has no effect.
:::note Legacy routes
The bare /api/agents/... memory routes and the /api/v1/agents/... memory write routes still work, but they are deprecated. Responses from them carry Deprecation and Link headers, plus a Sunset header once a removal date is set. See the legacy migration guide for the full mapping.
:::
For background on how memory works, see Memory System.
List Memories
GET /api/v1/characters/{character_id}/players/{player_id}/memories
Returns a paginated list of memories, newest first.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
memoryType | string | -- | Filter by type: "fact", "preference", "relationship", "event", "emotional_state", "correction", "character_self", "spatial_change" |
status | string | "active" | Memory status: "active", "superseded", "decayed", "deleted", or "quarantined" |
limit | integer | 50 | Results per page (1-200) |
offset | integer | 0 | Number of results to skip (minimum 0) |
Results are returned newest first. This endpoint does not accept sort parameters.
Response
{
"memories": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"userId": "user_def456",
"agentId": "550e8400-e29b-41d4-a716-446655440001",
"playerId": "player_xyz",
"memoryType": "fact",
"content": "The user works as a software engineer at a startup in San Francisco",
"confidence": 0.92,
"status": "active",
"source": "text_chat",
"memoryLayer": "ltm",
"importance": 0.78,
"sourceConversationId": "conv_def456",
"sourceQuote": "I work as a software engineer at this startup in SF",
"accessCount": 3,
"createdAt": "2026-02-10T15:30:00",
"lastAccessedAt": "2026-03-01T14:22:00",
"extractedAt": "2026-02-10T15:31:00",
"sourceMessageTimestamp": "2026-02-10T15:29:50",
"updatedAt": "2026-02-10T15:30:00"
}
],
"total": 87,
"limit": 50,
"offset": 0
}
Memory Object Fields
| Field | Type | Description |
|---|---|---|
id | string | Unique memory identifier (UUID) |
userId | string | ID of the developer account that owns the memory |
agentId | string | The character this memory belongs to. The field keeps its older name, but the value is the same character_id used in the URL path |
playerId | string | Player ID this memory was extracted for |
memoryType | string | One of: fact, preference, relationship, event, emotional_state, correction, character_self, spatial_change |
content | string | The memory content text |
confidence | number | Confidence score (0.0-1.0) |
status | string | "active", "superseded", "decayed", "deleted", or "quarantined" |
source | string | Origin of the memory: "text_chat" (auto-extracted from a conversation) or "manual_dashboard" (created via the Create Memory endpoint) |
memoryLayer | string | "ltm" (long-term) or "stm" (short-term). Controls how the memory ages -- short-term memories fade faster if unused |
importance | number | Importance weight (0.0-1.0) used during retrieval ranking |
sourceConversationId | string | null | Conversation that produced this memory. null for manually created memories |
sourceQuote | string | null | Original conversation text that produced this memory. null for manually created memories |
accessCount | number | How many times this memory has been retrieved |
createdAt | string | ISO 8601 creation timestamp |
lastAccessedAt | string | null | ISO 8601 timestamp of last retrieval |
extractedAt | string | null | ISO 8601 timestamp of extraction |
sourceMessageTimestamp | string | null | ISO 8601 timestamp of the source message |
updatedAt | string | null | ISO 8601 last update timestamp |
id, agentId, playerId, content, source, importance and memoryLayer are always present. Every other field may be null.
Examples
# All memories
curl "https://api.estuary-ai.com/api/v1/characters/{character_id}/players/player_xyz/memories" \
-H "X-API-Key: est_your_api_key"
# Only fact memories
curl "https://api.estuary-ai.com/api/v1/characters/{character_id}/players/player_xyz/memories?memoryType=fact" \
-H "X-API-Key: est_your_api_key"
const res = await fetch(
`https://api.estuary-ai.com/api/v1/characters/${characterId}/players/${playerId}/memories?memoryType=event&limit=20`,
{ headers: { "X-API-Key": "est_your_api_key" } }
);
const data = await res.json();
console.log(`${data.total} event memories`);
Create Memory
POST /api/v1/characters/{character_id}/players/{player_id}/memories
Create a memory manually -- useful for seeding context, importing data, or building a dashboard that lets end users teach the character.
Request Body
JSON, Content-Type: application/json. All keys are camelCase.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
content | string | Yes | -- | Memory text. 1-2000 characters |
memoryType | string | Yes | -- | One of: "fact", "preference", "relationship", "event", "emotional_state", "correction", "character_self", "spatial_change" |
importance | number | No | 0.5 | 0.0-1.0. Higher values are surfaced more aggressively during retrieval |
memoryLayer | string | No | "stm" | "ltm" for memories you want preserved long-term; "stm" for ephemeral ones |
confidence | number | No | 1.0 | 0.0-1.0. Defaults to 1.0 because manual entries are presumed accurate |
Unknown fields are rejected with 422 Unprocessable Entity.
Response
201 Created with the full Memory object. The created memory has source: "manual_dashboard" and sourceConversationId: null.
Quotas
Manual writes (create + update combined) are limited to 1,000 per day per developer account. Exceeding the cap returns 429 Too Many Requests:
{ "detail": "Manual memory write quota exceeded (1000/day)" }
The counter resets at UTC midnight.
Example
curl -X POST "https://api.estuary-ai.com/api/v1/characters/{character_id}/players/player_xyz/memories" \
-H "X-API-Key: est_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"content": "The user is allergic to peanuts.",
"memoryType": "fact",
"importance": 0.9,
"memoryLayer": "ltm"
}'
const res = await fetch(
`https://api.estuary-ai.com/api/v1/characters/${characterId}/players/${playerId}/memories`,
{
method: "POST",
headers: {
"X-API-Key": "est_your_api_key",
"Content-Type": "application/json",
},
body: JSON.stringify({
content: "The user is allergic to peanuts.",
memoryType: "fact",
importance: 0.9,
memoryLayer: "ltm",
}),
}
);
const memory = await res.json();
console.log("Created", memory.id);
Update Memory
PATCH /api/v1/characters/{character_id}/players/{player_id}/memories/{memory_id}
Partially update a memory. Send only the fields you want to change. Omitted fields are left untouched.
Request Body
Same field set as Create Memory -- all fields optional:
| Field | Type | Description |
|---|---|---|
content | string | New memory text (1-2000 chars) |
memoryType | string | New type |
importance | number | New importance (0.0-1.0) |
memoryLayer | string | "ltm" or "stm" |
confidence | number | New confidence (0.0-1.0) |
Unknown fields return 422. A body with no recognized fields is a no-op and returns the existing memory unchanged.
Response
200 OK with the updated Memory object.
Returns 404 Not Found if the memory doesn't exist or belongs to a different character or player. Counts toward the daily manual-write quota.
Example
curl -X PATCH "https://api.estuary-ai.com/api/v1/characters/{character_id}/players/player_xyz/memories/{memory_id}" \
-H "X-API-Key: est_your_api_key" \
-H "Content-Type: application/json" \
-d '{ "content": "The user is allergic to peanuts and tree nuts.", "importance": 0.95 }'
Delete a Memory
DELETE /api/v1/characters/{character_id}/players/{player_id}/memories/{memory_id}
Delete a single memory. By default this is a soft delete -- the memory's status flips to "deleted" and it is excluded from retrieval, but the row is preserved so it can be inspected later. Pass ?hard=true to remove the row entirely.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
hard | boolean | false | If true, permanently removes the memory. If false, marks it deleted but preserves the row |
Response
204 No Content on success. Empty body.
Returns 404 Not Found if the memory doesn't exist or belongs to a different character or player.
Example
# Soft delete (recoverable)
curl -X DELETE "https://api.estuary-ai.com/api/v1/characters/{character_id}/players/player_xyz/memories/{memory_id}" \
-H "X-API-Key: est_your_api_key"
# Hard delete (permanent)
curl -X DELETE "https://api.estuary-ai.com/api/v1/characters/{character_id}/players/player_xyz/memories/{memory_id}?hard=true" \
-H "X-API-Key: est_your_api_key"
Memory Timeline
GET /api/v1/characters/{character_id}/players/{player_id}/memories/timeline
Returns memories grouped by date for timeline visualization. The timeline covers the 500 most recent active memories.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
groupBy | string | "day" | Grouping period: "day", "week", "month" |
startDate | datetime | -- | Filter memories after this date (ISO 8601) |
endDate | datetime | -- | Filter memories before this date (ISO 8601) |
Response
{
"timeline": [
{
"date": "2026-03-01",
"memories": [ /* Memory objects */ ]
},
{
"date": "2026-02-28",
"memories": [ /* Memory objects */ ]
}
],
"totalMemories": 87,
"groupBy": "day"
}
Each date is YYYY-MM-DD (the day itself for "day" grouping, the Monday of the week for "week") or YYYY-MM for "month" grouping.
Example
curl "https://api.estuary-ai.com/api/v1/characters/{character_id}/players/player_xyz/memories/timeline?groupBy=week" \
-H "X-API-Key: est_your_api_key"
Memory Stats
GET /api/v1/characters/{character_id}/players/{player_id}/memories/stats
Returns aggregate statistics about a player's memory.
Response
{
"totalActive": 87,
"totalSuperseded": 5,
"totalDecayed": 3,
"byType": {
"fact": 32,
"preference": 15,
"relationship": 8,
"event": 18,
"emotional_state": 6,
"correction": 2,
"character_self": 4,
"spatial_change": 2
},
"coreFacts": 12,
"byLayer": {
"ltm": 61,
"stm": 26
},
"lastLayerTransferAt": "2026-03-01T04:00:00",
"recentPromotions7d": 4,
"recentDemotions7d": 1
}
| Field | Type | Description |
|---|---|---|
totalActive | number | Active memories |
totalSuperseded | number | Memories replaced by newer information |
totalDecayed | number | Memories that have decayed below threshold |
byType | object | Breakdown of active memories by type (type name to count) |
coreFacts | number | Number of core facts |
byLayer | object | Breakdown of active memories by layer (layer name to count) |
lastLayerTransferAt | string | null | ISO 8601 timestamp of the most recent layer change, or null if none has happened |
recentPromotions7d | number | Memories promoted from short-term to long-term in the last 7 days |
recentDemotions7d | number | Memories demoted from long-term to short-term in the last 7 days |
Example
curl "https://api.estuary-ai.com/api/v1/characters/{character_id}/players/player_xyz/memories/stats" \
-H "X-API-Key: est_your_api_key"
Core Facts
GET /api/v1/characters/{character_id}/players/{player_id}/memories/core-facts
Returns the core facts the character knows about a player. Core facts are concise, structured statements (name, location, interests, etc.) always included in the LLM context.
Response
{
"coreFacts": [
{
"id": "fact_001",
"userId": "user_abc",
"agentId": "agent_xyz",
"playerId": "player_xyz",
"factKey": "name",
"factValue": "Alex",
"sourceMemoryId": "mem_abc123",
"createdAt": "2026-02-10T15:30:00",
"updatedAt": "2026-03-01T14:22:00"
},
{
"id": "fact_002",
"userId": "user_abc",
"agentId": "agent_xyz",
"playerId": "player_xyz",
"factKey": "occupation",
"factValue": "Software engineer at a startup",
"sourceMemoryId": "mem_def456",
"createdAt": "2026-02-12T10:00:00",
"updatedAt": null
}
]
}
Example
curl "https://api.estuary-ai.com/api/v1/characters/{character_id}/players/player_xyz/memories/core-facts" \
-H "X-API-Key: est_your_api_key"
Knowledge Graph
GET /api/v1/characters/{character_id}/players/{player_id}/memories/graph
Returns the memory knowledge graph: clustered memories, entities, and their relationships.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
includeEntities | boolean | false | Include entity nodes and relationship edges |
includeCharacterMemories | boolean | false | Include the character's self-reflective memories |
Response
{
"nodes": [
{ "id": "user_player_xyz", "type": "user", "label": "player_xyz" },
{ "id": "cluster_0", "type": "cluster", "label": "Work & Career", "memoryCount": 12 },
{ "id": "mem_abc", "type": "memory", "label": "Works as a software engineer...", "memoryType": "fact" },
{ "id": "ent_001", "type": "entity", "entityType": "person", "name": "Alex" }
],
"edges": [
{ "source": "user_player_xyz", "target": "cluster_0", "type": "has_cluster" },
{ "source": "cluster_0", "target": "mem_abc", "type": "contains" },
{ "source": "mem_abc", "target": "ent_001", "type": "mentions" }
],
"stats": {
"totalMemories": 87,
"totalEntities": 15,
"clusterCount": 6,
"clusters": { "Work & Career": 12, "Hobbies": 8 }
},
"stale": false
}
stale is true when a cached graph was returned immediately while a fresh one is being computed. A request shortly afterwards should return the fresh version.
Node and edge objects are heterogeneous: the keys present depend on the node or edge type, and new keys may be added. Read the keys you need and tolerate the ones you don't recognize.
Node types
| Type | Description |
|---|---|
user | The player node (center of the graph) |
cluster | A topic group containing related memories |
memory | An individual memory |
entity | A person, place, or thing mentioned in memories |
Edge types
| Type | Description |
|---|---|
has_cluster | User -> Cluster |
contains | Cluster -> Memory |
mentions | Memory -> Entity |
relationship | Entity -> Entity (with relationshipType and label) |
Example
curl "https://api.estuary-ai.com/api/v1/characters/{character_id}/players/player_xyz/memories/graph?includeEntities=true" \
-H "X-API-Key: est_your_api_key"
Search Memories
POST /api/v1/characters/{character_id}/players/{player_id}/memories/search
Semantic search across a player's memories using vector similarity. The query is embedded once and matched against both text-derived and image-derived memories; results are merged and ranked by similarity.
:::note Search is a read
Despite the POST method, search is a read operation: an API key with only the memories:read scope can call it. The search text goes in the JSON body as query, not in a q query parameter, and a GET to this path returns 405 Method Not Allowed.
:::
Request Body
JSON, Content-Type: application/json.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
query | string | Yes | -- | Search query (1-500 chars) |
limit | integer | No | 10 | Max results (1-50) |
Unknown fields are rejected with 422 Unprocessable Entity.
Response
{
"results": [
{
"memoryId": "550e8400-e29b-41d4-a716-446655440000",
"similarity": 0.87,
"content": "The user talked about hiking in Rocky Mountain National Park last summer"
}
],
"query": "hiking trips",
"total": 1
}
| Field | Type | Description |
|---|---|---|
memoryId | string | ID of the matching memory. Pass to the List Memories endpoint (or filter the response) to get the full memory object |
similarity | number | Vector similarity score (higher = more relevant) |
content | string | The matching memory's content text, included for convenience |
Example
curl -X POST "https://api.estuary-ai.com/api/v1/characters/{character_id}/players/player_xyz/memories/search" \
-H "X-API-Key: est_your_api_key" \
-H "Content-Type: application/json" \
-d '{ "query": "hiking trips", "limit": 5 }'
const res = await fetch(
`https://api.estuary-ai.com/api/v1/characters/${characterId}/players/${playerId}/memories/search`,
{
method: "POST",
headers: {
"X-API-Key": "est_your_api_key",
"Content-Type": "application/json",
},
body: JSON.stringify({ query: "favorite foods", limit: 10 }),
}
);
const data = await res.json();
data.results.forEach((r) => {
console.log(`[${r.similarity.toFixed(2)}] ${r.content}`);
});
Delete All Memories
DELETE /api/v1/characters/{character_id}/players/{player_id}/memories?confirm=true
Permanently deletes all memories for a player-character pair. Useful for GDPR compliance and right-to-be-forgotten requests. To delete a single memory, use Delete a Memory instead.
The confirm=true query parameter is required. Requests without it return 400 Bad Request. This action cannot be undone.
Response
200 OK with the number of records removed:
{
"deletedCount": 87
}
Example
curl -X DELETE "https://api.estuary-ai.com/api/v1/characters/{character_id}/players/player_xyz/memories?confirm=true" \
-H "X-API-Key: est_your_api_key"