Memory API
Set up an integration, step by step
Every call in order, with the response value that lets you move to the next step.
Copy page
1. Get access and keys
Call the API at https://api.harny.ai/v1/memory. Send an app key in the Authorization: Bearer $HARNY_APP_KEY header. App keys start with fsa_. Add X-Harny-User: <user> to name the person whose memory you read or write. A user key starts with fsk_ and names its own user, so it does not need X-Harny-User.
Read the key from the environment variable HARNY_APP_KEY.
curl -X GET "https://api.harny.ai/v1/memory/usage" \
-H "Authorization: Bearer $HARNY_APP_KEY"The call returns 200 with the month's usage for the app. That response confirms the key. A wrong or missing key returns 401 unauthorized. Every error uses the body {"code", "status", "debugMessage", "data"}, and every response carries X-Request-Id. Errors lists the codes.
Move to step 2 when the usage call returns 200.
2. Decide the scope
Use one app per authorization domain. Each app key selects one app. Users are people. Each user has their own memory inside the app.
The JSON field projectId in responses carries the app id.
Move to step 3 when you know which apps you need and which users each one holds.
3. Group the users (optional)
An audience is a named group of users. Create each one with PUT /v1/memory/audiences/{slug}. A fixed audience names the users it holds. A rule audience matches all or any of 1 to 20 conditions on user traits. Every app also has a default audience, project, that holds every user.
Every data point goes to the default audience: the audience field takes only project or nothing. A group you create does not yet limit who reads a data point; to keep two customers apart, use one app for each. Skip this step if you do not need groups. Otherwise move to step 4 when GET /v1/memory/audiences lists every audience you created.
4. Create the users and their traits
Create each user with PUT /v1/memory/identities/{identity}, or up to 1,000 at once with POST /v1/memory/identities/bulk. A user has an identity, traits (a map of strings) and lastSeenAt. A trait key is up to 200 characters and a trait value up to 2,000.
When a call sends an identity in its body, it must equal the user the call acts as. Any other value returns 403 identity-mismatch.
Move to step 5 when GET /v1/memory/identities lists every user.
5. Cut the history into data points
A data point is one piece of raw text with its time. Each data point has these fields:
id: the source id. Defaults to the lowercase SHA-256 ofcontent. Must not contain/, and is at most 512 characters.content: required. Non-blank, no U+0000, at most 500,000 characters.label: at most 200 characters.timestamp: ISO 8601 with a zone. Defaults to the stored value, else the current time.metadata: a JSON object, at most 64 KiB serialized.identity: at most 200 characters.idempotencyKey: at most 200 characters.
An unknown field returns 400 invalid-ingestion. For a single data point, use POST /v1/memory/ingest. A response of 202 with status: "queued" means the send is accepted.
Cut one thing that happened into one data point. Sending data gives the sizes and the id rules. Move to step 6 when every data point has an id, a content and a timestamp.
6. Send the history
Send data points in batches with POST /v1/memory/ingest/batch. Each batch takes items (1 to 1,000 data points) and one idempotencyKey. Each batch is at most 16 MiB of UTF-8 in total. Each id may appear only once per batch.
The whole batch is validated before any write. After that, each item is written on its own. If an item is refused, the earlier items stay accepted. The error returns data: {ingestionId, accepted: k}. Retry the same batch with the same idempotencyKey to resume at item k.
The same idempotencyKey with the same body returns the original receipt. The same key with a different body returns 409 idempotency-conflict.
Response: 202 with ingestionId, status: "queued", and one entry per item in items. A 200 with status: "completed" and unchanged: true means every item was already stored as sent. Keep each batch's ingestionId for step 7.
7. Wait for each batch
Poll GET /v1/memory/ingest/{ingestionId} until the batch is done. The response has status, settled, blocked, readiness, and failure.
statusisprocessingwhile any item waits in the retry queue. It isfailedif any item is set aside after repeated failures. Otherwise it iscompleted.settledistruewhen the first threereadinesscounts (raw,comprehension,consolidation) are 0 andfailedis 0. The batch is finished.blockedistruewhen the first threereadinesscounts are 0 andfailedis above 0. The batch needs attention. Readfailurefor{code, message}.
Move to step 8 when settled is true for every batch. If blocked is true for one, call POST /v1/memory/ingest/{ingestionId}/retry, or read GET /v1/memory/ingest/{ingestionId}/explain first.
8. Verify with a recall
Call POST /v1/memory/recall with query and identity. Optional fields are limit (1 to 200, default 100), level or maxTokens (never both), queryTimestamp, occurredFrom, occurredTo, and sort.
The response contains asOf, usedEvidenceTokens, results, and pending. Each result has id, rank, occurredAt, content, artifact, and sources.
Check that your test data points appear in results. If pending is present, the user still has queued items. Wait for step 7 to finish, then recall again.
Move to step 9 when a recall for a fact you sent returns it in results[0], with the sourceId of the data point you sent.
9. Go live
Check three things, then send production traffic.
- Recall as a user who must not see a fact. The fact does not appear.
GET /v1/memory/usagereturns the app's count for the month.- Your application stores the
X-Request-Idof every call, so the debugging reads can find a call later.