AgentRAM API Reference
Everything your AI agent needs to read and write persistent memory. All endpoints return JSON. Authentication is handled via an API key in the request header.
pip install agentram-sdk and JavaScript/TypeScript npm install agentram-sdk. The examples below map one-to-one to the SDK methods. Full usage on the Python and npm package pages.
Quickstart
Three steps to give your agent a working memory.
Step 1: Register, and we email your API key
curl -X POST https://api.agentram.dev/register \ -H "Content-Type: application/json" \ -d '{"email":"you@example.com"}'
{
"success": true,
"message": "Check your email for your API key."
}
Step 2: Store a memory
Use the API key from your welcome email in the x-api-key header (shown below as agentram_7f3a9b2c...).
curl -X POST https://api.agentram.dev/memory \ -H "x-api-key: agentram_7f3a9b2c..." \ -H "Content-Type: application/json" \ -d '{"agent_id":"agent-01","key":"user_pref","value":"prefers short answers"}'
Step 3: Read it back in any later session
curl https://api.agentram.dev/memory?agent_id=agent-01&key=user_pref \ -H "x-api-key: agentram_7f3a9b2c..."
Authentication
Every request except /register requires your API key in the request header.
x-api-key: agentram_your_key_here
Requests without a valid API key return a 401 Unauthorized response. Keep your API key private. Treat it like a password.
Credits
AgentRAM uses a credit system. Each operation costs one credit. Credits are deducted only on success. If a request fails for any reason, your credit is returned automatically.
| Operation | Credit cost |
|---|---|
| POST /memory | 1 credit |
| GET /memory | 1 credit |
| GET /memories | 1 credit |
| GET /memory/search | 1 credit |
| DELETE /memory | 1 credit |
| POST /memory/shared | 1 credit |
| GET /memory/shared | 1 credit |
| GET /memories/shared | 1 credit |
| POST /namespace | 0 credits |
| GET /credits | 0 credits |
| POST /register | 0 credits |
| POST /recover | 0 credits |
| POST /recover/confirm | 0 credits |
GET /credits at any time. Top up at agentram.dev/#pricing.
Endpoints
Register
Creates a new account and emails you an API key with 1,000 free credits. For security, the key is sent by email rather than in the response, and the response is identical whether or not the email is already registered. One account per email address.
| Body param | Type | Required | Description |
|---|---|---|---|
| string | required | Your email address. Used to identify your account. |
{
"success": true,
"message": "Check your email for your API key."
}
Write a memory
Stores a value under a key for a given agent. Writing to an existing key updates the value. No duplicates are created.
| Body param | Type | Required | Description |
|---|---|---|---|
| agent_id | string | required | A unique identifier for the agent. Max 100 characters. |
| key | string | required | The memory label. Max 200 characters. |
| value | string | required | What to remember. Max 5,000 characters. |
| ttl_days | number | optional | Days until this memory expires automatically. Once expired, reads return 404 and the memory is excluded from lists and searches. Omit for memories that should not expire. |
{
"success": true,
"data": {
"agent_id": "agent-01",
"key": "user_pref",
"credits_remaining": 999
}
}
Read a memory
Retrieves a stored value by agent ID and key. Returns 404 if the memory does not exist, and refunds the credit.
| Query param | Type | Required | Description |
|---|---|---|---|
| agent_id | string | required | The agent identifier used when writing the memory. |
| key | string | required | The memory label to retrieve. |
{
"success": true,
"data": {
"agent_id": "agent-01",
"key": "user_pref",
"value": "prefers short answers",
"created_at": "2026-05-09T18:00:00.000Z",
"credits_remaining": 998
}
}
Delete a memory
Permanently removes a stored memory. This cannot be undone.
| Body param | Type | Required | Description |
|---|---|---|---|
| agent_id | string | required | The agent identifier. |
| key | string | required | The memory label to delete. |
{
"success": true,
"data": { "deleted": true, "credits_remaining": 997 }
}
Check credit balance
Returns the current credit balance for your account. This endpoint never deducts a credit. Safe to call as often as needed.
{
"success": true,
"data": {
"email": "you@example.com",
"credits": 997
}
}
List all memories for an agent
Returns all memories stored under a given agent ID, ordered by most recently written. Expired memories are silently excluded.
| Query param | Type | Required | Description |
|---|---|---|---|
| agent_id | string | required | The agent identifier. |
| limit | integer | optional | Max results to return. Default 50, max 200. |
{
"success": true,
"data": [
{ "key": "user_lang", "value": "French", "created_at": "...", "expires_at": null },
{ "key": "tone", "value": "formal", "created_at": "...", "expires_at": null }
],
"credits_remaining": 996
}
Search memories by text
Case-insensitive text search across both the key and value fields for a given agent. Returns matching memories ordered by most recent. No embeddings or configuration required.
| Query param | Type | Required | Description |
|---|---|---|---|
| agent_id | string | required | The agent identifier. |
| q | string | required | The search term. Matched against key and value fields. |
{
"success": true,
"data": [
{ "key": "user_lang", "value": "French", "created_at": "..." }
],
"credits_remaining": 995
}
Shared memory
Shared namespaces allow multiple agents or API key holders to read and write to a common memory pool. Create a namespace first, then share the namespace_key with any agent that needs access.
Create a namespace
Creates a new shared namespace and returns a unique namespace key. Free, costs no credits. Any agent with a valid API key and the namespace key can then read and write to that namespace.
| Body param | Type | Required | Description |
|---|---|---|---|
| label | string | optional | A human-readable name for the namespace. Max 100 characters. |
{
"success": true,
"data": { "namespace_key": "ns_a1b2c3d4...", "label": "team-alpha" }
}
Write a shared memory
Writes a value to a shared namespace. Any API key holder who knows the namespace key can write. Supports optional TTL.
| Body param | Type | Required | Description |
|---|---|---|---|
| namespace_key | string | required | The namespace key returned from POST /namespace. |
| key | string | required | The memory label. Max 200 characters. |
| value | string | required | What to store. Max 5,000 characters. |
| ttl_days | number | optional | Days until this memory expires automatically. |
Read a shared memory
Retrieves a value from a shared namespace by key.
| Query param | Type | Required | Description |
|---|---|---|---|
| namespace_key | string | required | The namespace key. |
| key | string | required | The memory label to retrieve. |
List all shared memories
Returns all memories in a shared namespace, ordered by most recent. Expired memories are silently excluded.
| Query param | Type | Required | Description |
|---|---|---|---|
| namespace_key | string | required | The namespace key. |
| limit | integer | optional | Max results. Default 50, max 200. |
Account recovery
If you lose your API key, these two endpoints issue a replacement by email. Both are free and require no authentication. The flow is: call POST /recover with your email, receive a link by email, then that link's page calls POST /recover/confirm with the token to issue a new key. Most developers never call these directly, since the recovery page handles it, but they are documented here for completeness.
Request a recovery link
Sends a single-use recovery link to the given email if an account exists. Always returns the same generic success response, whether or not the email is registered, to prevent anyone discovering which addresses have accounts.
| Body param | Type | Required | Description |
|---|---|---|---|
| string | required | The email address on the account you want to recover. |
{
"success": true,
"message": "If this email exists in our system, you'll receive recovery instructions shortly."
}
The recovery link expires 30 minutes after it is sent and can be used only once. Requesting a new link invalidates any previous one for the same email.
Confirm a recovery link
Validates a recovery token and issues a new API key for the account. The token is consumed on first use, so a second call with the same token is rejected. Because keys are stored hashed and never in plain text, the original key cannot be shown again, so recovery rotates it instead. The old key stops working immediately, which also means a leaked key is revoked the moment you recover. Update anywhere the old key was deployed.
| Body param | Type | Required | Description |
|---|---|---|---|
| token | string | required | The token from the recovery link (the value after ?token= in the emailed URL). |
{
"success": true,
"data": {
"email": "you@example.com",
"api_key": "agentram_7f3a9b2c...",
"credits": 1000
}
}
A used, expired, or invalid token returns success: false with a message explaining that a new link must be requested. Recovering also sends a notification email to the account, so an unexpected recovery is visible to the owner.
Temporal memory
POST /memory overwrites in place, which is what you want for most things. Some facts have a history that matters: the last invoice number, the model an agent is using, the deploy target for a project. When one of those changes you often want to know what it used to be, who changed it, and when.
Assertions are an append-only log for exactly that. Each write records a value plus who wrote it and which earlier value it replaced. Reads return the value that is currently true, and the full chain stays available.
Assertions are a separate keyspace. An assertion named invoice_number and a memory named invoice_number are unrelated and do not see each other. Use flat memory for values you are happy to overwrite, and assertions for values whose history you care about.
Assert a fact
Records a value as a new assertion. Pass supersedes to say which earlier assertion this one replaces. Without it the write simply appends: if something is already live for the key, the key becomes contested and the next read returns 409 rather than guessing between the two.
| Body param | Type | Required | Description |
|---|---|---|---|
| agent_id | string | required | A unique identifier for the agent. |
| key | string | required | The fact label. Max 256 characters. |
| value | string | required | The value being asserted. |
| supersedes | uuid | optional | The assertion_id this write replaces. It must be a live assertion under the same agent and key. Superseding also clears any other live assertion for that key, which is how a contested key gets resolved. |
| valid_until | string | optional | An ISO 8601 instant when the fact stops being true. Past that moment it no longer appears as current. |
| ttl_days | number | optional | The same thing as a rolling window from now. Give valid_until or ttl_days, never both. |
| written_by | string | optional | Provenance: who wrote this, usually an agent name. Max 256 characters. |
| context | string | optional | Provenance: a project or session tag. Max 256 characters. |
{
"success": true,
"data": {
"assertion_id": "9c1f... ",
"agent_id": "agent-01",
"key": "invoice_number",
"value": "1044",
"state": "live",
"written_by": "billing-agent",
"written_at": "2026-07-31T09:14:22Z"
},
"credits_remaining": 998
}
Returns 409 if the assertion named in supersedes is no longer live, meaning another writer changed the key first. Re-read the key and retry. Returns 400 if it does not reference an assertion under this agent and key.
Retire a fact
Ends a fact with nothing replacing it. Use this when something stopped being true and no new value took its place. Afterwards the key has no current value, but the history survives: the retirement is itself recorded as an assertion with its own author and timestamp, so you can always see who ended it and when. That is the difference from DELETE /memory, which erases and leaves no trace.
| Body param | Type | Required | Description |
|---|---|---|---|
| agent_id | string | required | A unique identifier for the agent. |
| key | string | required | The fact to retire. |
| written_by | string | optional | Who retired it. |
| context | string | optional | A project or session tag. |
{
"success": true,
"data": {
"assertion_id": "4b7e... ",
"key": "invoice_number",
"state": "retired",
"cleared": 1
},
"credits_remaining": 997
}
cleared is how many live assertions the retirement ended. More than one means the key was contested at that moment. Returns 404 if nothing was live to retire.
Read the current fact
Returns the assertion that is currently true for a key, with its provenance and the assertion_id you need to supersede it. Returns 404 when nothing is live, whether the key was never asserted, was retired, or passed its valid_until.
| Query param | Type | Required | Description |
|---|---|---|---|
| agent_id | string | required | A unique identifier for the agent. |
| key | string | required | The fact to read. |
| resolve | string | optional | Only last-write-wins is accepted. On a contested key it returns the newest value instead of a 409. Any other value is rejected with 400. Safe to pass on every read, since it does nothing unless there is a conflict. |
{
"success": true,
"data": {
"key": "invoice_number",
"value": "1044",
"assertion_id": "9c1f... ",
"written_by": "billing-agent",
"written_at": "2026-07-31T09:14:22Z"
},
"credits_remaining": 996
}
When a key is contested. If two agents write the same key without either knowing about the other, there are two live values and neither replaced the other. Returning one of them would be a guess, and silently reading a stale value is the exact failure assertions exist to prevent. So the read returns 409 with both, newest first, and you decide.
{
"success": false,
"error": "Key has conflicting live assertions",
"conflict": {
"key": "invoice_number",
"assertions": [
{ "assertion_id": "9c1f... ", "value": "1044", "written_by": "agent-a" },
{ "assertion_id": "3d80... ", "value": "1099", "written_by": "agent-b" }
]
}
}
Resolve it by asserting a new value that supersedes one of them. The others stop being current at the same time, so the key is settled in one write.
List current facts
Everything currently true for an agent, one entry per key. Retired keys and keys past their valid_until do not appear.
| Query param | Type | Required | Description |
|---|---|---|---|
| agent_id | string | required | A unique identifier for the agent. |
| resolve | string | optional | Only last-write-wins. Fills in values for contested keys instead of withholding them. |
| limit | number | optional | Default 50, maximum 200. |
{
"success": true,
"data": {
"assertions": [
{
"key": "deploy_target",
"value": "production",
"contested": false,
"assertion_count": 1,
"assertion_id": "7a22... ",
"written_by": "deploy-agent"
},
{
"key": "invoice_number",
"contested": true,
"assertion_count": 2
}
],
"truncated": false
},
"credits_remaining": 995
}
A contested key is flagged and its value is withheld, for the same reason the single-key read refuses one: guessing across a list is the same mistake as guessing on one key. Read that key on its own to see the competing values. truncated is true when more entries exist beyond the limit.
Read a fact's history
Every assertion ever written for a key, newest first. This is the audit trail: what each value was, who wrote it, when, and which one it replaced. It is what turns "why does the agent still think that?" into something you can read.
| Query param | Type | Required | Description |
|---|---|---|---|
| agent_id | string | required | A unique identifier for the agent. |
| key | string | required | The fact whose history you want. |
| limit | number | optional | Default 50, maximum 200. |
{
"success": true,
"data": {
"key": "invoice_number",
"assertions": [
{ "value": "1044", "state": "live", "written_by": "agent-a" },
{ "value": "1043", "state": "superseded", "written_by": "agent-a" }
],
"truncated": false
},
"credits_remaining": 994
}
state is live for the current value, superseded for one that was replaced, and retired for a retirement marker.
Error codes
All errors return a consistent shape with a success: false flag and a plain-language message.
| Status | Meaning | Common cause |
|---|---|---|
| 200 | Success | Request completed, memory read or written. |
| 201 | Created | New account registered successfully. |
| 400 | Bad request | Missing or invalid body parameters. |
| 401 | Unauthorized | Missing or invalid API key. |
| 402 | Payment required | Credit balance is zero. Top up to continue. |
| 404 | Not found | Memory does not exist. Credit refunded. |
| 409 | Conflict | Two live assertions share a key and neither replaces the other, or the assertion you tried to supersede is no longer current. Credit refunded. See GET /assertion. |
| 429 | Rate limited | Too many requests. Slow down and retry. |
| 500 | Server error | Something went wrong on our end. Try again shortly. |
{
"success": false,
"error": "Insufficient credits"
}
Rate limits
All memory endpoints share one simple limit: 60 requests per minute, per API key. That is fast enough to sit inside an agent loop without getting in your way. A few sensitive endpoints have their own stricter limits to prevent abuse, listed below.
| Endpoint | Limit | Window |
|---|---|---|
| All memory endpoints | 60 requests | Per minute, per API key |
| POST /register | 10 requests | Per hour, per IP |
| POST /recover | 3 requests | Per hour, per email |
| POST /recover | 10 requests | Per hour, per IP |
The 60-per-minute limit covers storing, retrieving, listing, searching, deleting, shared-namespace operations, and credit checks, everything except registration and recovery. Rate-limited requests return a 429 status. Credits are not deducted for rate-limited requests.
Code examples
Python
# pip install requests import requests API_KEY = "agentram_your_key_here" BASE = "https://api.agentram.dev" HEADERS = {"x-api-key": API_KEY, "Content-Type": "application/json"} # Write a memory requests.post(f"{BASE}/memory", json={ "agent_id": "agent-01", "key": "user_language", "value": "French" }, headers=HEADERS) # Read it back res = requests.get(f"{BASE}/memory", params={"agent_id": "agent-01", "key": "user_language"}, headers=HEADERS) print(res.json()["data"]["value"]) # → "French"
JavaScript / Node
const API_KEY = "agentram_your_key_here"; const BASE = "https://api.agentram.dev"; const headers = { "x-api-key": API_KEY, "Content-Type": "application/json" }; // Write a memory await fetch(`${BASE}/memory`, { method: "POST", headers, body: JSON.stringify({ agent_id: "agent-01", key: "tone", value: "formal" }) }); // Read it back const res = await fetch(`${BASE}/memory?agent_id=agent-01&key=tone`, { headers }); const { data } = await res.json(); console.log(data.value); // → "formal"