https://api.pumadb.ai
Agent memory API docs
REST API for server-side agent memory.
Use pumaDB from backend routes, workers, serverless functions, CLIs, and scripts when your app or agent needs durable JSON memory. A puma_live_* API key is a bearer secret for server-side code; do not put it in React bundles, static sites, mobile apps, public repos, or browser-executed code.
Authorization: Bearer puma_live_...
https://api.pumadb.ai/mcp
Quickstart
Create a server-side memory key.
Request a magic link, open the email verification link or POST its token, then copy the returned api_key. Use that initial key to create a named key for each app or environment, and store the app key in server-side environment variables such as PUMADB_API_KEY.
curl -X POST https://api.pumadb.ai/auth/magic-link \
-H 'Content-Type: application/json' \
-d '{"email":"you@example.com"}'
# Open the email link, or copy its token and verify it:
curl -X POST https://api.pumadb.ai/auth/verify \
-H 'Content-Type: application/json' \
-d '{"token":"<token-from-magic-link>"}'
# The verify response includes {"api_key":"puma_live_..."}.
export PUMADB_API_KEY="puma_live_..."
curl -X POST https://api.pumadb.ai/v1/_keys \
-H 'Authorization: Bearer $PUMADB_API_KEY' \
-H 'Content-Type: application/json' \
-d '{"name":"my-react-api-prod"}'
Calls
Write and read memory rows.
Rows are JSON objects and receive id, created_at, and updated_at. Filters are top-level equality checks on string, number, boolean, or null values.
curl -X POST https://api.pumadb.ai/v1/tasks \
-H 'Authorization: Bearer $PUMADB_API_KEY' \
-H 'Content-Type: application/json' \
-d '{"title":"ship docs","status":"open"}'
const response = await fetch("https://api.pumadb.ai/v1/tasks?limit=25", {
headers: {
Authorization: `Bearer ${process.env.PUMADB_API_KEY}`
}
});
if (!response.ok) {
throw new Error(await response.text());
}
const { rows } = await response.json();
Success check: You are done when GET /v1/tasks returns the row you just created.
Language quickstarts
Run one complete file.
Each example checks for PUMADB_API_KEY, writes one tasks row, reads rows back, and fails if the created row is missing.
TypeScript / Nodequickstart.ts
// quickstart.ts
// Run with: PUMADB_API_KEY=puma_live_... npx tsx quickstart.ts
const apiKey = process.env.PUMADB_API_KEY;
if (!apiKey) {
throw new Error("Set PUMADB_API_KEY before running this script.");
}
type PumaRow = {
id: string;
title: string;
status: string;
created_at: string;
updated_at: string;
};
async function puma<T>(path: string, init: { method?: string; body?: string } = {}): Promise<T> {
const response = await fetch("https://api.pumadb.ai" + path, {
method: init.method,
body: init.body,
headers: {
Authorization: "Bearer " + apiKey,
"Content-Type": "application/json"
}
});
if (!response.ok) {
throw new Error(await response.text());
}
return response.json() as Promise<T>;
}
const created = await puma<PumaRow>("/v1/tasks", {
method: "POST",
body: JSON.stringify({ title: "ship docs", status: "open" })
});
const result = await puma<{ rows: PumaRow[] }>("/v1/tasks?limit=25");
const found = result.rows.find((row) => row.id === created.id);
if (!found) {
throw new Error("Created row was not returned by GET /v1/tasks.");
}
console.log("created=" + created.id + " fetched=" + found.title + " status=" + found.status);
// Expected output:
// created=row_... fetched=ship docs status=open
Pythonquickstart.py
# quickstart.py
# Run with: PUMADB_API_KEY=puma_live_... python3 quickstart.py
import json
import os
import urllib.error
import urllib.request
BASE_URL = "https://api.pumadb.ai"
API_KEY = os.environ.get("PUMADB_API_KEY")
if not API_KEY:
raise SystemExit("Set PUMADB_API_KEY before running this script.")
def puma(path, method="GET", body=None):
data = None if body is None else json.dumps(body).encode("utf-8")
request = urllib.request.Request(
BASE_URL + path,
data=data,
method=method,
headers={
"Authorization": "Bearer " + API_KEY,
"Content-Type": "application/json",
},
)
try:
with urllib.request.urlopen(request) as response:
return json.loads(response.read().decode("utf-8"))
except urllib.error.HTTPError as error:
raise RuntimeError(error.read().decode("utf-8")) from error
created = puma("/v1/tasks", "POST", {"title": "ship docs", "status": "open"})
result = puma("/v1/tasks?limit=25")
found = next((row for row in result["rows"] if row["id"] == created["id"]), None)
if not found:
raise RuntimeError("Created row was not returned by GET /v1/tasks.")
print({"created": created["id"], "fetched": found["title"], "status": found["status"]})
Goquickstart.go
// quickstart.go
// Run with: PUMADB_API_KEY=puma_live_... go run quickstart.go
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
)
const baseURL = "https://api.pumadb.ai"
type Row struct {
ID string `json:"id"`
Title string `json:"title"`
Status string `json:"status"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
type QueryResponse struct {
Rows []Row `json:"rows"`
}
func puma(method, path string, body any, out any) error {
var reader io.Reader
if body != nil {
encoded, err := json.Marshal(body)
if err != nil {
return err
}
reader = bytes.NewReader(encoded)
}
req, err := http.NewRequest(method, baseURL+path, reader)
if err != nil {
return err
}
req.Header.Set("Authorization", "Bearer "+os.Getenv("PUMADB_API_KEY"))
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
data, err := io.ReadAll(res.Body)
if err != nil {
return err
}
if res.StatusCode < 200 || res.StatusCode >= 300 {
return fmt.Errorf("%s", data)
}
return json.Unmarshal(data, out)
}
func main() {
if os.Getenv("PUMADB_API_KEY") == "" {
panic("Set PUMADB_API_KEY before running this program.")
}
var created Row
err := puma("POST", "/v1/tasks", map[string]string{
"title": "ship docs",
"status": "open",
}, &created)
if err != nil {
panic(err)
}
var result QueryResponse
if err := puma("GET", "/v1/tasks?limit=25", nil, &result); err != nil {
panic(err)
}
for _, row := range result.Rows {
if row.ID == created.ID {
fmt.Printf("created=%s fetched=%s status=%s\n", created.ID, row.Title, row.Status)
return
}
}
panic("Created row was not returned by GET /v1/tasks.")
}
React apps
Use a backend or serverless proxy.
A plain React/static app should call your own API route, and that route should call pumaDB with the API key. pumaDB does not expose browser CORS for /v1, because current API keys are full-access bearer secrets.
app.post("/api/tasks", async (req, res) => {
const response = await fetch("https://api.pumadb.ai/v1/tasks", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.PUMADB_API_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify(req.body)
});
res.status(response.status).send(await response.text());
});
Endpoints
Current REST surface.
POST/auth/magic-linkRequest a magic link.
GET/auth/verify?token=...Open the email link and show the created API key.
POST/auth/verifyExchange a magic-link token for an API key as JSON.
POST/v1/{table}Add a JSON row.
GET/v1/{table}Query rows with filter, sort, and limit.
POST/v1/{table}/batchRun atomic same-table write operations.
POST/v1/{table}/upsertInsert or update by natural key.
POST/v1/{table}/update_rowUpdate one existing row by id.
POST/v1/{table}/update_whereUpdate rows matching a non-empty filter.
DELETE/v1/{table}Delete rows matching a non-empty filter.
GET/v1/{table}/countCount rows.
GET/v1/{table}/versionsList archived row versions.
POST/v1/{table}/restoreRestore an archived row version.
GET/v1/_tablesList tables and row counts.
GET/v1/_exportExport all tables.
POST/v1/_row_linksCreate a short-lived JSON viewer/editor link for one stored row.
POST/v1/_query_linksCreate a short-lived read-only JSON viewer link for one query result.
POST/v1/_text_field_linksCreate a short-lived viewer/editor link for one stored text field.
POST/v1/orgs/{orgId}/_row_linksCreate an organization row viewer/editor link that requires current org membership when opened.
POST/v1/orgs/{orgId}/_query_linksCreate an organization query viewer link that requires current org membership when opened.
POST/v1/orgs/{orgId}/_text_field_linksCreate an organization text-field viewer/editor link that requires current org membership when opened.
GET/v1/_keysList API keys.
POST/v1/_keysCreate a named API key.
DELETE/v1/_keys/{id}Revoke an API key.
MCP
Use MCP for agent connectors.
Hosted MCP remains available at https://api.pumadb.ai/mcp with OAuth discovery and dynamic client registration. Use REST for app backends and MCP for agent memory tools. Headless agents should use REST unless their MCP client can complete the OAuth flow. See the tool-call reference below for the full agent-facing surface.
MCP tools
Tool-call reference.
These are the tools exposed by the hosted MCP server and local MCP package. They map to the same pumaDB row operations as the REST API, plus safe helper tools for common agent memory types.
Table operations
Create, read, update, count, and clean up JSON rows in named tables.
add
Add a new JSON row to a table. Use when every submission should create a new row. Include an idempotency key when a retry should not duplicate the write.
query
Read rows from a table. Filter by simple equality, sort by one field, and cap results with a limit. MCP query results add short-lived viewer/download links for large text, larger result sets, or explicit includeLink requests.
batch
Run several writes in one request. Use for multiple add, upsert, update_row, update_where, and delete operations against one table. Batches are atomic and capped at 100 operations.
upsert
Insert or replace by natural key. Best for save-or-replace workflows where creating a missing row is acceptable. Do not use it to rename or change key values.
update_row
Patch one existing row by id. Use when you already know the row id. It never creates a row.
update_where
Patch rows matching a non-empty filter. Best for natural edits like changing a stored value. Defaults to exactly one match; bulk updates require allowMultiple.
list_tables
List tables and row counts. Use to inspect what an account has already stored.
count
Count rows in a table. Optionally count only rows matching a simple equality filter.
delete
Delete matching rows. Explicit cleanup only. Requires a non-empty filter and should be used only when the user clearly asks to remove matching memory.
Version history
Recover from overwrites and deletes with archived row versions.
versions
List archived versions of a row. Updates and deletes archive prior content. The last 10 versions are kept for 30 days.
restore
Restore a row to an archived version. Recreates the row if it was deleted. The current value is archived first, so a restore can itself be undone.
Safe memory helpers
Store common agent memory types as inert JSON with safety metadata.
remember
Store typed memory through one consolidated tool. Preferred for new agent writes. Use type resource, code, markdown, command, or config; compatibility remember_* tools store the same inert row shapes.
remember_resource
Store a URL, media/file reference, or documentation note. Stores references plus metadata only; it does not fetch URLs, install dependencies, execute code, or store large binary files.
open_row
Open a stored row in a short-lived JSON viewer/editor. Use when a user wants to inspect, download, or manually edit a complete JSON row outside the chat transcript.
open_text_field
Open a stored text field in a short-lived viewer/editor. Use when a user wants to inspect, download, or manually edit a full Markdown, code, command, or config field without pasting the entire value through chat.
remember_code
Store code or configuration text as an inert snippet. Records text only. SQL snippets get conservative metadata for statement type and destructive risk.
remember_markdown
Store Markdown notes, prompts, instructions, or skill drafts. Markdown is stored as inert text with active_instructions false until a user explicitly asks to use it.
remember_command
Store a shell command as inert text. Records the command, shell, working directory, risk label, and tags without executing anything.
remember_config
Store configuration file content as inert text. Useful for MCP configs, env files, TOML, JSON, YAML, and CI snippets. It is not applied or written to disk.