API
The xmemory REST API is what the Python and TypeScript SDKs call under the hood. You can also call it directly with curl, fetch, or any HTTP client.
For MCP-based access (no HTTP calls needed), see the MCP guide.
All endpoints live under https://api.xmemory.ai, accept JSON, and return JSON. Every request except /healthz must send your xmemory API key as a Bearer token: Authorization: Bearer <your API key>.
Get your API key from the xmemory console. Copy and securely store it — never share your API key publicly.
Response envelope
Section titled “Response envelope”Every endpoint wraps its result in a standard envelope:
{ "ids": ["<created-resource-id>"], "items": [ { "…": "the actual payload" } ], "errors": [], "console_url": "https://console.xmemory.ai/…"}On a successful response, the per-endpoint shapes documented below (write_id, reader_result, data_schema, …) are the object inside items[0] — a raw HTTP caller must read them there, not at the top level. ids collects created resource IDs, errors any non-fatal errors, and console_url links to the relevant view in the console. (The Python and TypeScript SDKs unwrap this for you.) Schema-evolution endpoints report failures differently: they return a structured error body at the top level rather than in this envelope (see Structured errors).
Try it
Section titled “Try it”Write something, then read it back — two curl calls:
# Writecurl -X POST https://api.xmemory.ai/instances/$XMEM_INSTANCE_ID/write \ -H "Authorization: Bearer $XMEM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "text": "Alice Johnson works at Acme Corp. Her email is alice@acme.com.", "extraction_logic": "deep" }'
# Readcurl -X POST https://api.xmemory.ai/instances/$XMEM_INSTANCE_ID/read \ -H "Authorization: Bearer $XMEM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "query": "What is Alice'\''s email?" }'# → {"ids": [], "items": [{"reader_result": {"answer": "alice@acme.com"}}], "errors": [], "console_url": "…"}The rest of this page covers every endpoint in detail. The data-plane endpoints (write, read, extract, describe) come first; if you don’t have an instance yet, jump to Clusters and Instances to create one.
Writing
Section titled “Writing”Send raw text; xmemory extracts typed objects from it and persists them. write blocks until committed; write_async returns immediately and you poll write_status for completion. Both endpoints alternatively accept structured_mutations for deterministic, extraction-free edits — see Structured writes.
Endpoints
POST/instances/{instance_id}/writePersist data — extract structured objects from text, or apply explicit structured mutations (blocks until committed)
Parameters
textstringtext / structured_mutations must be provided.structured_mutationsarraytext.extraction_logicstring"fast" (default) or "deep". Text writes only. Defaults to "fast".trace_idstringsession_idstringuse_diff_enginebooleanReturns
write_idstringcleaned_objectsobject{ objects: [...], relations: [...] }.changesobjectcreated / updated / deleted (each { objects: [...], relations: [...] }; updated entries carry per-field old/new values).trace_idstringExample response
{ "write_id": "...", "cleaned_objects": { "objects": [], "relations": [] }, "changes": { "created": { "objects": [], "relations": [] }, "updated": { "objects": [], "relations": [] }, "deleted": { "objects": [], "relations": [] } }, "trace_id": "..."}POST/instances/{instance_id}/write_asyncSame write pipeline as write, but returns immediately instead of blocking
Parameters
textstringtext / structured_mutations must be provided.structured_mutationsarraytext.extraction_logicstring"fast" (default) or "deep". Text writes only. Defaults to "fast".trace_idstringsession_idstringuse_diff_enginebooleanReturns
write_idstringwriteStatus. Do not read immediately after an async write — the data may not be committed yet.Example response
{ "write_id": "..." }POST/instances/{instance_id}/write_statusCheck whether an async write has finished
Parameters
write_idstringrequiredwriteAsync.Returns
write_statusstringqueued, processing, completed, failed, not_found — plus the intermediate two-phase-pipeline states extracting, extracted, applying. Treat anything other than completed / failed / not_found as still in flight.error_detailstringwrite_status is failed; otherwise null.errorobjectcode, message) when the write failed; otherwise null.completed_atstringExample response
{ "write_id": "...", "write_status": "completed", "error_detail": null, "error": null, "completed_at": "2025-03-16T12:34:56Z"}Structured writes
Section titled “Structured writes”When you already know exactly what to store, skip extraction entirely: pass structured_mutations instead of text (exactly one of the two — the request is rejected if both or neither are present). Each mutation is applied deterministically, with no LLM involved, making structured writes fast and exactly repeatable.
A mutation is tagged object_mutation or relation_mutation and carries exactly one operation — create, update, or delete:
curl -X POST https://api.xmemory.ai/instances/$XMEM_INSTANCE_ID/write \ -H "Authorization: Bearer $XMEM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "structured_mutations": [ { "object_mutation": { "object_type": "person", "create": { "key": { "email": "alice@acme.com" }, "values": { "name": "Alice Johnson" } } } }, { "relation_mutation": { "relation_type": "works_at", "create": { "endpoints": [ { "object_name": "person", "key": { "email": "alice@acme.com" } }, { "object_name": "company", "key": { "name": "Acme Corp" } } ] } } } ] }'Addressing objects. key identifies an object by its user-defined primary-key fields (as above) — or, for update / delete, by { "xuid": "..." } (the server-generated unique ID returned in changes). create never takes an xuid; it is generated server-side.
Object operations.
create—key(primary-key fields) plusvalues(the remaining fields).update—keyplusvalues; anullvalue clears that field.delete—keyonly.
Relation operations.
create—endpoints(one per relation role:object_name+ that object’skey) plus optional own-fieldvalues.update— address byendpoints, or bykey: { "xuid": ... }when endpoints are ambiguous;valuesas for objects.delete— match byendpoints(a subset is allowed) orkey; deleting more than one matched row requires"allow_bulk_delete": true.
Ordering and batches. Mutations apply in list order, and later mutations may reference objects created earlier in the same batch (e.g. create a person, then a relation pointing at it). Multiple mutations of the same object or relation in one batch follow a strict contract: compatible sequences compose (create then update applies the update to the created row; update then delete nets out to a delete; duplicate deletes are no-ops), while contradictory ones — create then delete, or anything after a delete — are rejected with a “split into separate writes” error rather than silently reordered.
Response. The same shape as a text write; changes reports exactly what was created / updated / deleted, with per-field old/new values on updates. write_async accepts structured_mutations identically — poll writeStatus and read changes from the completed status.
Availability. Structured writes can be disabled per deployment; when off, the request fails with "Structured writes are not enabled on this deployment."
Reading
Section titled “Reading”Ask a question in natural language — xmemory searches the instance’s stored objects and returns a synthesized answer.
Endpoints
POST/instances/{instance_id}/readQuery the instance in natural language
Parameters
querystringrequiredmodestring"single-answer" (default), "xresponse", or "raw-tables". Defaults to "single-answer".scopeobject{ "objects": [...], "relations_scope": "no_relations" | "all_relations" }. Defaults to no_relations (objects only). See Scoped reads.return_sqlbooleansql). Defaults to false.skip_suggestion_capturebooleanfalse.trace_idstringsession_idstringReturns
The reader_result shape depends on the mode:
| Mode | reader_result |
Best for |
|---|---|---|
single-answer |
{"answer": "Bob Lee joined last Monday."} |
Natural-language answers |
xresponse |
{"objects": [...], "relations": [...]} |
Structured data |
raw-tables |
{"tables": [...]} |
Raw SQL results |
Also returned: sql (the executed SQL, when return_sql: true),
pending_suggestions (count of pending schema improvements), and trace_id.
Scoped reads
Section titled “Scoped reads”Restrict a query to a specific set of objects in your instance, limiting data access to only what you specify. Useful for multi-tenant systems, privacy controls, and compliance requirements.
Request format
Section titled “Request format”Add a scope field to the /read endpoint:
{ "query": "What is their email?", "scope": { "objects": [ { "type": "Person", "key": {"email": "alice@example.com"} } ], "relations_scope": "no_relations" }}Scope structure
Section titled “Scope structure”objects (required)
Section titled “objects (required)”List of one or more objects to query. Each object has:
type(string): The object type (e.g.,"Person","Order").key(object): Identify the object by its primary key fields (e.g.,{"email": "alice@example.com"}).
relations_scope (optional, default: "no_relations")
Section titled “relations_scope (optional, default: "no_relations")”Control whether to include relations that these objects are part of:
"no_relations": Only the specified objects."all_relations": Include relations these objects are part of.
Examples
Section titled “Examples”Query a single object
Section titled “Query a single object”curl -X POST https://api.xmemory.ai/instances/your-instance-id/read \ -H "Authorization: Bearer $XMEM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "query": "What is their email and phone number?", "scope": { "objects": [ {"type": "Person", "key": {"name": "Alice Johnson"}} ] } }'Query multiple objects with relations
Section titled “Query multiple objects with relations”curl -X POST https://api.xmemory.ai/instances/your-instance-id/read \ -H "Authorization: Bearer $XMEM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "query": "Which projects do these people work on together?", "scope": { "objects": [ {"type": "Person", "key": {"email": "alice@example.com"}}, {"type": "Person", "key": {"email": "bob@example.com"}} ], "relations_scope": "all_relations" } }'Semantics: The scope is limited to the two specified Person objects. With "relations_scope": "all_relations", relations these Person objects are part of become visible.
Error handling
Section titled “Error handling”If the scope is invalid, the API returns HTTP 400:
{ "status": "error", "error_message": "Unknown scope object type 'Foo'; valid types are Person, Project"}Extracting
Section titled “Extracting”Preview the objects a write would produce — same extraction pipeline, but nothing is stored.
Endpoints
POST/instances/{instance_id}/extractExtract structured objects from text without storing them — preview what a write would produce
Parameters
textstringrequiredextraction_logicstring"fast" (default) or "deep". Same parameters as write. Defaults to "fast".trace_idstringsession_idstringReturns
objects_extractedobject{ objects: [...], relations: [...] }.trace_idstringExample response
{ "objects_extracted": { "objects": [], "relations": [] }, "trace_id": "..." }Describing
Section titled “Describing”Return the agent-facing tool descriptions for an instance, enriched with its live schema.
Endpoints
GET/instances/{instance_id}/describeReturn agent-facing tool descriptions enriched with the instance's actual schema
Returns
instance_idstringinstance_namestringschema_summarystringtoolsarrayExample response
{ "instance_id": "...", "instance_name": "contacts", "schema_summary": "This instance tracks contacts with name, email, ...", "tools": [ { "name": "write", "description": "Extract structured data from text and persist it.", "when_to_use": "When you need to store new facts ...", "parameters": [ { "name": "text", "type": "string", "description": "...", "required": true }, { "name": "extraction_logic", "type": "string", "description": "...", "required": false, "enum": ["fast", "deep"], "default": "fast" } ], "http_method": "POST", "http_path": "/instances/.../write" } ]}Clusters
Section titled “Clusters”Clusters are the top-level container that holds instances. List the clusters you can access, or fetch one by ID.
Endpoints
GET/clustersList all clusters accessible to the authenticated user
Returns
An array of clusters, each { id, org_id, name, description }.
Example request
curl https://api.xmemory.ai/clusters \ -H "Authorization: Bearer $XMEM_API_KEY"GET/clusters/{cluster_id}Get a single cluster by ID
Returns
A single cluster { id, org_id, name, description }.
Example request
curl https://api.xmemory.ai/clusters/$XMEM_CLUSTER_ID \ -H "Authorization: Bearer $XMEM_API_KEY"Schema generation
Section titled “Schema generation”Turn a plain-language description into a typed xmemory schema — the first step before creating an instance.
Endpoints
POST/clusters/{cluster_id}/instances/generate_schemaGenerate an xmemory schema from a plain-language description
Parameters
schema_descriptionstringrequiredcurrent_yml_schemastringReturns
data_schemaobjectcurrent_yml_schema is omitted (create-from-scratch), only data_schema is returned.migration_planobjectsummarystringwarningsarrayrepair_logarrayExample response
{ "data_schema": { "...": "..." }, "migration_plan": { "ops": [ { "op_type": "rename_field", "object_name": "person", "old_name": "mail", "new_name": "email" } ] }, "summary": "Rename person.mail to person.email.", "warnings": [], "repair_log": []}Instances
Section titled “Instances”An instance is a memory store with a typed schema, living inside a cluster — create one before you write or read. These endpoints manage its whole lifecycle: create, list, fetch, update its schema or metadata, and delete.
Endpoints
POST/clusters/{cluster_id}/instancesCreate an instance from a generated schema
Parameters
namestringrequireddescriptionstringinstance_schemaobjectrequired"yml": { "value": "..." } for YAML schemas, or "json_schema": { "value": "..." } for JSON.schema_descriptionstringgenerate_trace_idstringgenerateSchema call that produced its schema.Returns
idstringcluster_idstringnamestringdescriptionstringdata_schemaobjectExample response
{ "id": "...", "cluster_id": "...", "name": "contacts", "description": null, "data_schema": { "...": "..." }}GET/instancesList all instances accessible to the authenticated user
Returns
An array of instances (see getInstance for the per-item shape).
Example request
curl https://api.xmemory.ai/instances \ -H "Authorization: Bearer $XMEM_API_KEY"GET/instances/{instance_id}Get a single instance by ID
Returns
idstringcluster_idstringnamestringdescriptionstringcreated_onstringcreated_by_emailstringdata_schemaobjectGET/instances/{instance_id}/schemaGet the current schema of an instance
Returns
data_schemaobjectPUT/instances/{instance_id}/schemaUpdate the schema of an existing instance
Parameters
instance_schemaobjectrequiredmigration_planobjectgenerateSchema.confirm_destructivebooleantrue for any op that drops data. Defaults to false.Returns
migration_idstringprior_versionintegernew_versionintegermigration_warningsarrayErrors
A non-additive change sent without a plan is rejected with non_additive_change_requires_plan. An op that drops data without confirm_destructive: true returns destructive_confirmation_required. See Structured errors.
PUT/instances/{instance_id}Update instance metadata (name and description)
Parameters
namestringdescriptionstringReturns
idstringnamestringdescriptionstringDELETE/instances/{instance_id}Delete an instance
Returns
A list of deleted instance IDs.
Schema evolution
Section titled “Schema evolution”xmemory can change a live schema without losing data. There are two paths, both ending in an atomic migration with an audit record: a direct migration you drive yourself, and a suggestion engine that proposes improvements from real read traffic. Additive-only callers are unaffected — the endpoints above keep working as before.
Endpoints
POST/instances/{instance_id}/migrations/dry_runPreview a migration without applying it
Parameters
instance_schemaobjectrequiredmigration_planobjectgenerateSchema.confirm_destructivebooleantrue for any op that drops data. Defaults to false.Returns
statementsarraycurrent_versionintegerplan_summaryobject{ count_by_op_type, total }.requires_metadata_syncbooleanExample response
{ "status": "ok", "instance_id": "...", "current_version": 4, "statements": ["ALTER TABLE person RENAME COLUMN mail TO email"], "warnings": [], "plan_summary": { "count_by_op_type": { "rename_field": 1 }, "total": 1 }, "requires_metadata_sync": true}Errors
A non-additive change without a plan returns non_additive_change_requires_plan; a data-dropping op without confirmation returns destructive_confirmation_required. See Structured errors.
GET/instances/{instance_id}/migrationsList applied migrations, newest first
Parameters
limitinteger50.before_idstringinclude_yamlbooleanfalse.Returns
itemsarraygetMigration for the per-item shape). source is direct or suggestion_engine.next_before_idstringbefore_id to fetch the next (older) page; null on the last page.has_morebooleanExample response
{ "status": "ok", "items": [ { "id": "...", "applied_at": "...", "source": "suggestion_engine", "decided_by": "...", "prior_version": 3, "new_version": 4, "ops": [ ], "ops_summary": { } } ], "next_before_id": null, "has_more": false}GET/instances/{instance_id}/migrations/{migration_id}Get a single migration record
Parameters
include_yamlbooleanyaml_before / yaml_after). Defaults to false.Returns
idstringapplied_atstringsourcestringdirect or suggestion_engine.prior_versionintegernew_versionintegeropsarrayops_summaryobjectyaml_beforestringinclude_yaml: true).yaml_afterstringinclude_yaml: true).Suggestion engine — review → decide → apply
Section titled “Suggestion engine — review → decide → apply”The engine surfaces a single rolling proposal per instance, derived on demand from accumulated read-gap signals. The minimum flow is three calls.
Endpoints
POST/instances/{instance_id}/suggestions/reviewReturn the consolidated proposal and a proposal_version concurrency token
Parameters
session_idstring{} or { "session_id": "..." }.Returns
proposalobjectproposal_version (concurrency token), schema_version, and items.Example response
{ "status": "ok", "instance_id": "...", "proposal": { "proposal_version": "ab12...", "schema_version": 4, "items": [ { "item_fingerprint": "fp1", "op": { "op_type": "add_field", "object_name": "person", "field_name": "phone", "field_type": "str" }, "rationale": "queried but missing", "frequency": 3, "depends_on": [], "evidence_query_samples": ["what is bob's phone"] } ] }}Errors
When a migration is already running, the response is { "status": "evolution_in_progress", "retry_after_seconds": 5 } — back off and retry.
POST/instances/{instance_id}/suggestions/decideRecord an accept / reject / defer per item in one batch
Parameters
proposal_versionstringrequiredproposal_version from review.decisionsarrayrequired{ "item_fingerprint": "...", "decision": "accept" | "reject" | "defer" } per item.session_idstringReturns
The recorded decisions, advisory dependency warnings, and a next_proposal_version you can pass straight to apply.
Example response
{ "proposal_version": "ab12...", "decisions": [ { "item_fingerprint": "fp1", "decision": "accept" } ]}Errors
A proposal_version that no longer matches the current proposal returns stale_proposal_version; malformed decisions return invalid_decision_input. See Structured errors.
POST/instances/{instance_id}/suggestions/applyCommit accepted decisions as one migration
Parameters
proposal_versionstringrequirednext_proposal_version from decide.session_idstringReturns
On success, { "status": "ok", "migration_id": "...", "prior_version": 4, "new_version": 5, "applied_items": ["fp1"], "summary": "..." }, or { "status": "nothing_to_apply" } if no accepted items remained.
Example response
{ "status": "ok", "migration_id": "...", "prior_version": 4, "new_version": 5, "applied_items": ["fp1"], "summary": "..." }Errors
A stale token returns stale_proposal_version; an unmet dependency returns dependency_closure_failed; a failed commit returns apply_failed. See Structured errors.
POST/instances/{instance_id}/pending-feedback/discardClear the pending schema-improvement backlog for this instance
Returns
rows_discardedintegerinstance_idstringExample response
{ "status": "ok", "instance_id": "...", "rows_discarded": 12 }Details
Bulk-supersedes the pending schema-improvement backlog. Idempotent — discarded signals never resurface, and calling it with nothing pending is a safe no-op.
Structured errors
Section titled “Structured errors”Schema-evolution endpoints return a structured error body with a 4xx/5xx status:
{ "status": "error", "error_type": "stale_proposal_version", "error_message": "...", "details": { ... } }error_type is one of: non_additive_change_requires_plan, destructive_confirmation_required, stale_schema_version, migration_precondition_failed, migration_execution_failed, migration_not_found, stale_proposal_version, dependency_closure_failed, invalid_decision_input, apply_failed, instance_not_initialised. The SDKs surface error_type as a structured error code.
Limits
Section titled “Limits”No backfill of historical data into new fields; no rollback of a committed migration (atomic abort on failure is the safety story); identity tightening (change_object.new_primary_key, change_relation.new_keys) is relax-only.
Health check
Section titled “Health check”Endpoints
GET/healthzReturns 200 if the API is reachable (no authentication required)
Example request
curl https://api.xmemory.ai/healthzErrors
Section titled “Errors”All endpoints return an error response on failure with the appropriate HTTP status code (400, 401, 404, 500).