TypeScript
The xmemory npm package is a lightweight TypeScript client for the xmemory API. Zero dependencies — it uses native fetch only.
For MCP-based integration, see the MCP guide. For framework-specific setup, see Mastra AI.
API key: To use xmemory APIs or integrations, you need an API key. Please register your interest at https://xmemory.ai and we will reach out to give access. Copy and securely store the key. Never share your API key publicly.
Installation
Section titled “Installation”npm install xmemoryQuick start
Section titled “Quick start”import { XmemoryClient, SchemaType } from "xmemory";
// Connect (reads XMEM_API_URL and XMEM_API_KEY from env)const xm = new XmemoryClient({ apiKey: "your-api-key" });
// Get a handle for an existing instanceconst inst = xm.instance("your-instance-id");
// Write some informationawait inst.write("Alice Johnson works at Acme Corp. Her email is alice@acme.com.");await inst.write("Bob Lee is a designer at Globex. He joined last Monday.");
// Read it backconst result = await inst.read("What is Alice's email?");console.log(result.reader_result);// → {"answer": "alice@acme.com"}Creating a client
Section titled “Creating a client”Three options, depending on whether you want a health check:
import { XmemoryClient, xmemoryInstance } from "xmemory";
// Factory function — runs a health check automaticallyconst xm = await xmemoryInstance({ url: "https://api.xmemory.ai", apiKey: "..." });
// Static method — same behaviorconst xm = await XmemoryClient.create({ url: "https://api.xmemory.ai", apiKey: "..." });
// Constructor — no health checkconst xm = new XmemoryClient({ url: "https://api.xmemory.ai", apiKey: "..." });| Option | Env var | Default | Description |
|---|---|---|---|
apiKey | XMEM_API_KEY | undefined | API key for authentication |
url | XMEM_API_URL | https://api.xmemory.ai | API base URL |
timeoutMs | — | 60000 | Default request timeout in milliseconds |
All three forms resolve apiKey and url from environment variables when not passed. Only xmemoryInstance() and XmemoryClient.create() perform a health check on construction.
Instance handle
Section titled “Instance handle”Use xm.instance(id) to get a scoped handle for data operations:
const inst = xm.instance("your-instance-id");
// All data operations go through the handleawait inst.write("Carol is a senior engineer at Initech.");const result = await inst.read("Who works at Initech?");console.log(result.reader_result);Writing
Section titled “Writing”Send free-form text — xmemory extracts structured objects according to your schema and merges them into the knowledge graph.
const resp = await inst.write("Carol is a senior engineer at Initech.");console.log(resp.write_id);console.log(resp.cleaned_objects);console.log(resp.trace_id); // request trace id when availableExtraction logic
Section titled “Extraction logic”Control the speed/accuracy tradeoff:
await inst.write("...", { extractionLogic: "fast" });| Value | When to use |
|---|---|
"deep" | Important or complex information (default) |
"fast" | High-volume, low-stakes writes |
Async writes
Section titled “Async writes”For latency-sensitive code, enqueue a write and return immediately:
const { write_id } = await inst.writeAsync("Dave manages the London office.");console.log(write_id); // use this to poll for completionwrite() and read() responses include trace_id, which is useful for correlating client calls with API logs.
Then check the status:
const status = await inst.writeStatus(write_id);console.log(status.write_status);// → "queued" | "processing" | "completed" | "failed" | "not_found"Do not call read immediately after writeAsync — the data may not be committed yet. Poll with writeStatus until "completed", or use write (synchronous) when you need to read right after.
Reading
Section titled “Reading”Ask questions in natural language:
const resp = await inst.read("Who works at Acme Corp?");console.log(resp.reader_result);console.log(resp.trace_id);Read modes
Section titled “Read modes”// Plain-text answer (default)const resp = await inst.read("What is Alice's email?", { readMode: "single-answer" });
// Structured objects and relationsconst resp = await inst.read("Show all contacts", { readMode: "xresponse" });
// Raw SQL result setsconst resp = await inst.read("List all contacts", { readMode: "raw-tables" });Extracting (without writing)
Section titled “Extracting (without writing)”Preview what xmemory would extract from a piece of text, without storing anything:
const resp = await inst.extract("Dave manages the London office.");console.log(resp.objects_extracted);console.log(resp.trace_id);Accepts the same extractionLogic option as write.
Describing (agent tool discovery)
Section titled “Describing (agent tool discovery)”The describe() method returns agent-facing tool descriptions enriched with the instance’s actual schema. Use it to tell an LLM what tools are available and how to call them.
const desc = await inst.describe();
// Plain text — inject into a system promptconsole.log(desc.asText());
// Anthropic tool-use formatconst tools = desc.asAnthropicTools();
// OpenAI function-calling formatconst tools = desc.asOpenaiTools();Results are cached locally for 5 minutes. To force a refresh (e.g. after updating the schema):
inst.clearDescribeCache();const desc = await inst.describe();asText() shows tools as method signatures by default. Pass { includeHttp: true } to also show HTTP method and path for raw REST callers.
Cluster and instance management
Section titled “Cluster and instance management”All management operations live on xm.admin:
Listing clusters
Section titled “Listing clusters”const clusters = await xm.admin.listClusters();const clusterId = clusters[0].id;Generating a schema
Section titled “Generating a schema”const schema = await xm.admin.generateSchema( clusterId, "Track contacts with name, email, company, and notes.",);console.log(schema.data_schema);Creating an instance
Section titled “Creating an instance”import { SchemaType } from "xmemory";
const inst = await xm.admin.createInstance( clusterId, "contacts", schemaYml, SchemaType.YML, { description: "User contacts" },);// inst is a bound InstanceHandle — use inst.write(), inst.read(), etc.await inst.write("Alice joined the team.");Updating a schema (additive)
Section titled “Updating a schema (additive)”For a purely additive change (new objects/fields/relations), update directly:
await xm.admin.updateInstanceSchema(instanceId, newSchemaYml, SchemaType.YML);Evolving a schema (rename / remove / type change)
Section titled “Evolving a schema (rename / remove / type change)”Non-additive changes preserve data via a structured migration plan. Use
enhanceSchema to get the plan, dryRunMigration to preview the DDL, then
updateInstanceSchema to apply it. confirmDestructive: true is required for
ops that drop data.
import yaml from "js-yaml";
const current = (await xm.admin.getInstanceSchema(instanceId)).data_schema;
// 1. Enhance — new schema + an executor-ready migration plan.const enhanced = await xm.admin.enhanceSchema( clusterId, "Rename Person.mail to Person.email.", yaml.dump(current),);console.log(enhanced.summary, enhanced.migration_plan?.ops);
const newYaml = yaml.dump(enhanced.data_schema);
// 2. Dry-run — preview the DDL, apply nothing.const preview = await xm.admin.dryRunMigration(instanceId, newYaml, SchemaType.YML, { migrationPlan: enhanced.migration_plan ?? undefined,});console.log(preview.statements);
// 3. Update — apply. A rename is non-destructive, so confirmDestructive stays false.const info = await xm.admin.updateInstanceSchema(instanceId, newYaml, SchemaType.YML, { migrationPlan: enhanced.migration_plan ?? undefined, confirmDestructive: false,});console.log(info.migration_id, info.prior_version, "->", info.new_version);Suggestion engine (review → decide → apply)
Section titled “Suggestion engine (review → decide → apply)”xmemory watches read traffic and, on demand, surfaces a single rolling proposal of schema improvements. The flow is three calls — never wrap them; the checkpoints are the point.
import type { DecisionInput } from "xmemory";
const inst = xm.instance(instanceId);
// 1. Review — proposal + optimistic-concurrency token.const review = await inst.reviewSuggestions();if (review.status === "evolution_in_progress") { console.log(`Migration in flight; retry in ${review.retry_after_seconds}s`);} else if (review.proposal) { for (const item of review.proposal.items) { console.log(item.item_fingerprint, item.rationale, item.op); }
// 2. Decide — accept / reject / defer per item, in bulk. const decisions: DecisionInput[] = review.proposal.items.map((i) => ({ item_fingerprint: i.item_fingerprint, decision: "accept", })); const decided = await inst.decideSuggestions(review.proposal.proposal_version, decisions);
// 3. Apply — commit accepted decisions as one migration. const applied = await inst.applyPendingDecisions(decided.next_proposal_version); console.log(applied.status, applied.summary);}Migration history
Section titled “Migration history”const page = await xm.admin.listMigrations(instanceId, { limit: 20 });for (const r of page.items) console.log(r.id, r.source, r.prior_version, "->", r.new_version);
const detail = await xm.admin.getMigration(instanceId, page.items[0].id, { includeYaml: true });console.log(detail.yaml_before, detail.yaml_after);Migration ops are a discriminated union on op_type (MigrationPlan,
AddField, RenameField, RemoveObject, …). ProposalItem.op and
MigrationRecord.ops stay raw for forward compatibility — narrow them to
MigrationOp when needed.
Listing and deleting instances
Section titled “Listing and deleting instances”const instances = await xm.admin.listInstances();const info = await xm.admin.getInstance(instanceId);
await xm.admin.deleteInstance(instanceId);Error handling
Section titled “Error handling”All errors throw XmemoryAPIError with optional .status (HTTP status), .code (structured error code, when the server returned one), and .details.
import { XmemoryAPIError, XmemoryHealthCheckError } from "xmemory";
try { const xm = await XmemoryClient.create({ apiKey: "..." });} catch (e) { if (e instanceof XmemoryHealthCheckError) { console.error("Server unreachable:", e.message); }}
try { await inst.write("...");} catch (e) { if (e instanceof XmemoryAPIError) { console.error(`Error (HTTP ${e.status}): ${e.message}`); }}The schema-evolution methods set .code to a structured error type you can match on — e.g. stale_proposal_version, destructive_confirmation_required, non_additive_change_requires_plan:
try { await inst.applyPendingDecisions(token);} catch (e) { if (e instanceof XmemoryAPIError && e.code === "stale_proposal_version") { const review = await inst.reviewSuggestions(); // re-review and retry }}Reference
Section titled “Reference”Client
Section titled “Client”| Method | Returns | Description |
|---|---|---|
new XmemoryClient(options?) | XmemoryClient | Create a client (no health check) |
XmemoryClient.create(options?) | Promise<XmemoryClient> | Create a client with health check |
xmemoryInstance(options?) | Promise<XmemoryClient> | Factory with health check (same as .create()) |
xm.instance(instanceId) | InstanceHandle | Get a handle for data operations on an instance |
xm.checkHealth() | Promise<void> | Raises XmemoryHealthCheckError if unreachable |
Admin methods (xm.admin)
Section titled “Admin methods (xm.admin)”| Method | Returns | Description |
|---|---|---|
listClusters(options?) | Promise<ClusterInfo[]> | List clusters |
getCluster(clusterId, options?) | Promise<ClusterInfo> | Get a cluster by ID |
generateSchema(clusterId, description, options?) | Promise<GenerateSchemaResult> | Generate a schema from a description |
createInstance(clusterId, name, schemaText, schemaType, options?) | Promise<InstanceHandle> | Create an instance; returns a bound handle |
listInstances(options?) | Promise<InstanceInfo[]> | List instances |
getInstance(instanceId, options?) | Promise<InstanceInfo> | Get an instance by ID |
deleteInstance(instanceId, options?) | Promise<string[]> | Delete an instance |
getInstanceSchema(instanceId, options?) | Promise<InstanceSchemaInfo> | Get an instance’s schema |
updateInstanceSchema(instanceId, schemaText, schemaType, options?) | Promise<InstanceInfo> | Update a schema; options accepts migrationPlan and confirmDestructive for non-additive changes |
updateInstanceMetadata(instanceId, name, description, options?) | Promise<InstanceInfo> | Update instance name and description. Both name and description are required strings. |
enhanceSchema(clusterId, schemaDescription, currentYmlSchema, options?) | Promise<EnhanceSchemaResult> | Evolve a schema → new schema + migration plan |
dryRunMigration(instanceId, schemaText, schemaType, options?) | Promise<DryRunResult> | Preview a migration’s DDL |
listMigrations(instanceId, options?) | Promise<ListMigrationsResult> | List applied migrations (limit, beforeId, includeYaml) |
getMigration(instanceId, migrationId, options?) | Promise<MigrationRecord> | Get a single migration record |
Instance methods (xm.instance(id))
Section titled “Instance methods (xm.instance(id))”| Method | Returns | Description |
|---|---|---|
write(text, options?) | Promise<WriteResult> | Extract and persist objects from text |
writeAsync(text, options?) | Promise<AsyncWriteResult> | Enqueue a write; returns write_id immediately |
writeStatus(writeId, options?) | Promise<WriteStatusResult> | Poll the status of an async write |
read(query, options?) | Promise<ReadResult> | Query the instance in natural language. options accepts readMode and traceId. |
extract(text, options?) | Promise<ExtractResult> | Extract objects without writing them |
getSchema(options?) | Promise<InstanceSchemaInfo> | Get this instance’s schema |
reviewSuggestions(options?) | Promise<ReviewSuggestionsResult> | Get the rolling schema-improvement proposal |
decideSuggestions(proposalVersion, decisions, options?) | Promise<DecideSuggestionsResult> | Record accept/reject/defer decisions in bulk |
applyPendingDecisions(proposalVersion, options?) | Promise<ApplyPendingDecisionsResult> | Apply accepted decisions as one migration |
describe(options?) | Promise<DescribeResult> | Get agent-facing tool descriptions (cached 5 min) |
clearDescribeCache() | void | Force next describe() to fetch fresh data |
id | string | The instance ID (property) |
type ExtractionLogic = "fast" | "deep";type ReadMode = "single-answer" | "raw-tables" | "xresponse";type WriteQueueStatus = "queued" | "processing" | "completed" | "failed" | "not_found";
const SchemaType = { YML: 0, JSON: 1 } as const;