MemorySync

Quickstart: Store a Fact, Then Ask for It Back

Create an API key, install the Python or TypeScript SDK, store one fact for an end user, query it back in other words, and handle skipped writes and errors.

Published on 6 min read

Quickstart: Store a Fact, Then Ask for It Back
On this page

An assistant that forgets what a user said yesterday makes that user repeat themselves. This guide stores one fact for one user and then asks for it back in different words, first in Python and then in TypeScript.

MemorySync is a hosted memory API. Your application stores memories scoped to an end user, and MemorySync returns the relevant ones ranked by meaning. The example end user is Sam, who mentioned a peanut allergy in an earlier conversation. You need Python 3.9 or later, or Node.js 18 or later.

Create a key and install

  1. Sign in to the dashboard, select a project and create a key under Settings → API Keys. A key created while a project is selected is locked to that project.
  2. Install the SDK for your language.
  3. Put the key in the MEMORYSYNC_API_KEY environment variable, so it never appears in your code.
Shell
pip install memorysync # Python
npm install memorysync-sdk # TypeScript or JavaScript
export MEMORYSYNC_API_KEY="ms_..."
# PowerShell: $env:MEMORYSYNC_API_KEY = "ms_..."

Keep the key on your server. Whoever holds it can name any end user in its project and read that user's memories, so it must never ship in a browser bundle or a mobile app.

If your key is organization-wide rather than locked to a project, also pass project_id (projectId in TypeScript) with the ID from the project menu in the dashboard header. The ID is proj_ followed by 16 hexadecimal characters.

Store a fact, then query it

Save this as quickstart.py and run python quickstart.py:

Python
import os
from memorysync import AddSkippedResponse, MemorySyncClient
with MemorySyncClient(
api_key=os.environ["MEMORYSYNC_API_KEY"],
base_url="https://api.memorysync.io",
end_user_id="sam",
) as ms:
result = ms.add("I am allergic to peanuts.")
if isinstance(result, AddSkippedResponse):
print("skipped:", result.reason)
else:
print("stored:", result.id, result.text)
found = ms.query("Does the user have any food allergies?", k=5)
for memory in found.memories:
print(memory.id, memory.text, memory.score)

What each part does:

  • MemorySyncClient takes your key, the API base URL and the end user. It sends the key as the X-API-Key header and the end user as X-End-User-ID on every request. api_key and base_url are both required.
  • add() sends the text to POST /memory/add and returns the outcome of the write.
  • query() sends the question to POST /memory/query. k=5 asks for at most five memories.
  • The with block closes the client's connections when it ends.

The question does not repeat the words of the fact. Results are ranked by meaning, so a question about food allergies can bring back a memory about peanuts.

What comes back

query() returns a QueryResponse. Its memories field is a list of Memory objects in ranked order, most relevant first. These are the fields you will use most:

FieldWhat it holds
idThe memory's integer id. Pass it to get(), or in a list to forget().
textThe memory as stored. Print it rather than assuming it matches your input word for word.
scoreHow well the memory matched this query. Higher is a closer match.
created_atWhen the memory was written.

add() returns one of two shapes. When MemorySync stores the input, you get a Memory with an id and text. When the input carries nothing worth keeping, MemorySync stores nothing and returns an AddSkippedResponse, and its reason field says why. A skip is a normal outcome, so it does not raise an exception.

Check the type before you read result.id. An AddSkippedResponse has no id field, which is why the example branches with isinstance first.

Both classes are importable from memorysync, next to the client. Branch on the result type for every write: a call that returned without an error has not necessarily stored anything.

In this example Sam has only the memory you just stored. Once an end user has more, the list can also include weaker matches ranked below the best one, up to k of them, so look at score before you pass a result on to a model.

The same steps in TypeScript

Save this as quickstart.mts and run it with npx tsx quickstart.mts. The .mts extension makes the file an ES module, which top-level await needs.

TypeScript
import { MemorySyncClient } from "memorysync-sdk";
const ms = new MemorySyncClient({
apiKey: process.env.MEMORYSYNC_API_KEY!,
baseUrl: "https://api.memorysync.io",
endUserId: "sam",
});
const result = await ms.add({ text: "I am allergic to peanuts." });
if ("status" in result && result.status === "skipped") {
console.log("skipped:", result.reason);
} else {
console.log("stored:", result.id, result.text);
}
const { memories } = await ms.query({ query: "Does the user have any food allergies?", k: 5 });
for (const memory of memories) {
console.log(memory.id, memory.text, memory.score);
}

The TypeScript client takes one options object per call and uses camelCase names: apiKey, baseUrl, endUserId. A skipped write comes back as an object with status: "skipped" and a reason, so the branch checks status instead of a class. Memory records use camelCase fields as well, such as createdAt.

Why every call names an end user

One API key serves all of your users, so every API-key call has to say which user it is for. That person is the end user, Sam in this guide. The client sends the id you gave it as X-End-User-ID on every request.

Leave it out and the API answers 400 with the error code MISSING_END_USER_ID, which both SDKs raise as ValidationError. An end-user id longer than 128 characters gets a 400 with INVALID_END_USER_ID.

Reads, searches and deletes are filtered to that end user, the key's project and the key's environment. A memory stored for an end user named alex does not come back when sam searches for it, even with the same key.

The environment comes from the key, never from the request. A development key and a production key see different data, so a fact written with one does not show up in queries made with the other. If a query comes back empty right after a write, check that both calls used the same key and the same end-user id.

Use an id that stays the same for the life of the person's account, such as the primary key from your users table. Changing the id creates a different memory scope. Avoid email addresses: people change them, and the id travels in a header on every request.

When a call fails

Every non-2xx response raises a subclass of MemorySyncError. The TypeScript SDK throws the same classes.

ExceptionRaised on
AuthError401 or 403: a bad key or a missing scope
ValidationError400, 409 or 422, including a missing end user
NotFoundError404: the record is not visible to this caller
RateLimitError429: read retry_after_seconds before retrying
ServerErrorAny 5xx response
MemorySyncErrorNetwork errors, timeouts and anything else

Each error carries status_code, response (the parsed body) and request_id. In TypeScript the names are statusCode, response and requestId, and the retry hint is retryAfterSeconds.

Python
import time
from memorysync import MemorySyncError, RateLimitError
def lookup(ms, question):
try:
return ms.query(question, k=5).memories
except RateLimitError as err:
time.sleep(err.retry_after_seconds)
return ms.query(question, k=5).memories
except MemorySyncError as err:
print("lookup failed:", type(err).__name__, err.status_code, err.request_id)
return []

Catch the specific classes first. They are subclasses of MemorySyncError, so an except MemorySyncError placed above them would catch everything. When a 429 response carries no retry hint, retry_after_seconds is 0.0, so add your own backoff for that case.

Log request_id. It comes from the x-request-id header on the response, and it is how support finds the exact request you are asking about. Errors that never got a response, such as timeouts, have no request id.

Next steps

The docs quickstart covers the same two calls with cURL. Before you pick end-user ids for production, read Scopes & End Users, which explains how the API key, the project and the end-user id fit together and how to choose a safe id.

Share article

Blog

Try it on your own workload

The free tier stores real memories and returns real rankings. One write and one retrieval to start.

Start free. No credit card required.