API Reference
Programmatic access to workspaces and memories. Organize, version, and serve project context to any LLM via REST.
Authentication
All API requests require a Bearer token in the Authorization header.
Content-Type: application/json. Responses use the same content type.
Read-Only Token
Access GET endpoints only. Perfect for AI tools that consume context.
Full Access Token
Access all endpoints — GET, POST, PATCH, DELETE. For management and automation.
Rate Limiting
API requests are limited to 100 requests per minute per token.
{
"error": {
"code": "RATE_LIMITED",
"message": "Rate limit exceeded. Please try again later.",
"status": 429
}
}Error Handling
All errors follow a consistent JSON structure with a code, message, and HTTP status.
{
"error": {
"code": "ERROR_CODE",
"message": "Human-readable description",
"details": { ... }, // optional, for validation errors
"status": 4xx
}
}| Status | Code | Description |
|---|---|---|
401 | UNAUTHORIZED | Invalid or missing access token |
403 | FORBIDDEN | Insufficient permissions or inactive workspace |
404 | NOT_FOUND | Resource not found |
422 | VALIDATION_ERROR | Invalid request data (includes details) |
429 | RATE_LIMITED | Rate limit exceeded |
Workspaces
List Workspaces
Returns all active workspaces for the current account.
| Name | Type | Description |
|---|---|---|
| page | integer | Page number (default: 1) |
[
{
"id": 1,
"name": "Project Alpha",
"description": "Main project workspace",
"memories_count": 42,
"archived": false,
"created_at": "2026-01-15T10:30:00Z",
"updated_at": "2026-02-04T14:22:00Z",
"url": "https://recuerd0.ai/workspaces/1"
}
]X-Page, X-Per-Page, X-Total, X-Total-Pages, and Link are included in all list responses.
Get Workspace
Returns a single workspace with memory count and pinned status.
{
"id": 1,
"name": "Project Alpha",
"description": "Main project workspace",
"memories_count": 42,
"archived": false,
"created_at": "2026-01-15T10:30:00Z",
"updated_at": "2026-02-04T14:22:00Z",
"url": "https://recuerd0.ai/workspaces/1"
}Get Workspace Context
Returns a compact "wake-up" snapshot of a workspace for AI agents — workspace metadata, the current user's pinned memories scoped to the workspace, and stats. Supports HTTP caching via ETag.
| Name | Type | Default | Description |
|---|---|---|---|
| limit | integer | 10 | Maximum pinned memories to return (1–50). |
| include_body | boolean | true | Whether to include each pinned memory's body content. |
| max_body_chars | integer | 500 | Maximum characters of body to return per memory (100–5000). Longer bodies are truncated. |
| category | string | Filter pinned memories to one category. See Memory Categories. |
{
"workspace": {
"id": 1,
"name": "Project Alpha",
"description": "Main project workspace",
"memories_count": 42,
"state": "active",
"updated_at": "2026-04-01T12:00:00Z",
"url": "https://recuerd0.ai/workspaces/1"
},
"pinned_memories": [
{
"id": 17,
"title": "Architecture Notes",
"source": "manual",
"category": "decision",
"tags": ["design", "core"],
"pinned_at": "2026-03-28T09:14:00Z",
"updated_at": "2026-04-01T11:42:00Z",
"url": "https://recuerd0.ai/workspaces/1/memories/17",
"body": "# Architecture\n\nThe system is split into…",
"body_truncated": true,
"links_count": 3
}
],
"stats": {
"total_memories": 42,
"total_pinned": 3,
"returned_pinned": 1
},
"generated_at": "2026-04-06T10:00:00Z"
}Create Workspace
Creates a new workspace.
| Name | Type | Description | |
|---|---|---|---|
| workspace[name] | string | required | Workspace name (max 100 characters) |
| workspace[description] | string | Workspace description |
{
"workspace": {
"name": "New Project",
"description": "A new workspace for the team"
}
}{
"id": 2,
"name": "New Project",
"description": "A new workspace for the team",
"memories_count": 0,
"archived": false,
"created_at": "2026-02-04T15:00:00Z",
"updated_at": "2026-02-04T15:00:00Z",
"url": "https://recuerd0.ai/workspaces/2"
}Update Workspace
Updates an existing workspace.
| Name | Type | Description |
|---|---|---|
| workspace[name] | string | Workspace name |
| workspace[description] | string | Workspace description |
Archive Workspace
Archives a workspace. Archived workspaces become read-only.
Unarchive Workspace
Restores an archived workspace.
Memories
Memory Categories
Every memory carries exactly one category from a locked four-value enum. Category defaults to general and is used to filter list, search, and workspace context endpoints. Versions inherit the parent memory's category unless overridden.
| Value | Label | Purpose |
|---|---|---|
decision | Decision | Architecture choices, library picks, tradeoffs with stated reasoning |
discovery | Discovery | Non-obvious findings — gotchas, root causes, patterns, library quirks |
preference | Preference | User-stated rules ("always X", "never Y") |
general | General | Catch-all and default |
category query parameter is supported on list, search, and workspace context endpoints to filter results to a single category.
Memory Links
Memory links — also called "tunnels" — are undirected, unlabeled "see also" connections between two memories within the same account. They cross workspace boundaries: a memory in your rails-app workspace can link to a memory in your mobile-app workspace. Use them to express that two memories cover the same topic without duplicating content.
| Method | Path | Description |
|---|---|---|
GET | /workspaces/:workspace_id/memories/:memory_id/links.json | List linked memories with their workspace info |
POST | /workspaces/:workspace_id/memories/:memory_id/links.json | Create a link to another memory by id |
DELETE | /workspaces/:workspace_id/memories/:memory_id/links/:id.json | Remove a link. :id is the other memory's id, not a join row id |
422. Storage is undirected: linking A↔B is the same as B↔A and the server normalizes the pair so the link is only created once. There is no cardinality limit, links carry no labels or metadata, and deleting a memory cascades to remove all of its links. The DELETE endpoint identifies the link by the other memory's id, not a join row id. Memory and pinned-memory JSON responses include a links_count field showing how many links a memory has.
List Memory Links
Returns all memories linked to a given memory, with each linked memory's workspace embedded.
[
{
"id": 118,
"title": "Auth strategy (mobile)",
"category": "decision",
"tags": ["auth", "mobile"],
"source": "manual",
"updated_at": "2026-03-22T11:05:00Z",
"url": "https://recuerd0.ai/workspaces/2/memories/118",
"workspace": {
"id": 2,
"name": "mobile-app",
"url": "https://recuerd0.ai/workspaces/2"
}
},
{
"id": 204,
"title": "Auth migration runbook",
"category": "discovery",
"tags": ["auth", "infra"],
"source": "manual",
"updated_at": "2026-04-02T08:30:00Z",
"url": "https://recuerd0.ai/workspaces/5/memories/204",
"workspace": {
"id": 5,
"name": "infra",
"url": "https://recuerd0.ai/workspaces/5"
}
}
]Create Memory Link
Creates an undirected link between this memory and another memory in the same account.
| Name | Type | Description | |
|---|---|---|---|
| to_memory_id | integer | required | The id of the other memory to link to. Must belong to the same account. |
{
"to_memory_id": 118
}{
"id": 118,
"title": "Auth strategy (mobile)",
"category": "decision",
"tags": ["auth", "mobile"],
"source": "manual",
"updated_at": "2026-03-22T11:05:00Z",
"url": "https://recuerd0.ai/workspaces/2/memories/118",
"workspace": {
"id": 2,
"name": "mobile-app",
"url": "https://recuerd0.ai/workspaces/2"
}
}422 if to_memory_id belongs to a different account, references the same memory, or the link already exists.
Delete Memory Link
Removes a link between two memories. The path :id is the other memory's id — not a separate join row id.
List Memories
Returns all memories (latest versions only) for a workspace. Supports filtering by title pattern, tags, and source.
| Name | Type | Description |
|---|---|---|
| page | integer | Page number (default: 1) |
| per_page | integer | Items per page (1–100, default: 25) |
| title | string | Glob pattern for title matching. * matches any characters, ? matches one |
| tags | string | Comma-separated tag list. Returns memories with ALL specified tags |
| source | string | Exact match on source field |
| category | string | Filter to one category. See Memory Categories. |
| sort | string | Sort field: updated_at (default), created_at, title |
| direction | string | Sort direction: desc (default), asc |
GET /workspaces/1/memories.json?title=Meeting*
GET /workspaces/1/memories.json?tags=api,design&sort=title&direction=asc
GET /workspaces/1/memories.json?category=decision&per_page=50[
{
"id": 1,
"title": "Meeting Notes",
"version": 1,
"source": "manual",
"category": "decision",
"tags": ["meetings", "q1"],
"created_at": "2026-01-20T09:00:00Z",
"updated_at": "2026-02-03T16:45:00Z",
"url": "https://recuerd0.ai/workspaces/1/memories/1"
}
]Browse Memories
Returns memories across all active workspaces for the current account. Supports the same filtering parameters as List Memories. JSON-only endpoint.
| Name | Type | Description |
|---|---|---|
| page | integer | Page number (default: 1) |
| per_page | integer | Items per page (1–100, default: 25) |
| workspace_id | integer | Filter to a specific workspace |
| title | string | Glob pattern for title matching. * matches any characters, ? matches one |
| tags | string | Comma-separated tag list. Returns memories with ALL specified tags |
| source | string | Exact match on source field |
| category | string | Filter to one category. See Memory Categories. |
| sort | string | Sort field: updated_at (default), created_at, title |
| direction | string | Sort direction: desc (default), asc |
GET /memories.json?title=*architecture*
GET /memories.json?tags=api,design&sort=title&direction=asc
GET /memories.json?workspace_id=1&source=manualGet Memory
Returns a memory with its content. Supports line range parameters to read specific portions.
| Name | Type | Description |
|---|---|---|
| line_start | integer | First line to return (1-based, inclusive). Default: 1 |
| line_end | integer | Last line to return (1-based, inclusive). Default: last line |
GET /workspaces/1/memories/1.json
GET /workspaces/1/memories/1.json?line_start=1&line_end=20
GET /workspaces/1/memories/1.json?line_start=50&line_end=75{
"id": 1,
"title": "Meeting Notes",
"version": 1,
"source": "manual",
"category": "decision",
"tags": ["meetings", "q1"],
"created_at": "2026-01-20T09:00:00Z",
"updated_at": "2026-02-03T16:45:00Z",
"url": "https://recuerd0.ai/workspaces/1/memories/1",
"content": {
"body": "# Meeting Notes\n\nDiscussed Q1 goals...",
"total_lines": 142,
"line_start": 1,
"line_end": 142
},
"links_count": 3,
"workspace": {
"id": 1,
"name": "Project Alpha",
"url": "https://recuerd0.ai/workspaces/1"
}
}content object always includes total_lines, line_start, and line_end. When line range parameters are provided, body contains only the requested lines. Returns 422 if line_start > line_end.
| Name | Type | Description |
|---|---|---|
| mode | string | Set to grep to search within the memory |
| q | string | Search query required |
| context | integer | Lines before and after each match (0–10, default: 0) |
| before | integer | Lines before match, overrides context (0–10) |
| after | integer | Lines after match, overrides context (0–10) |
GET /workspaces/1/memories/1.json?mode=grep&q=architecture
GET /workspaces/1/memories/1.json?mode=grep&q=design&context=2{
"id": 1,
"title": "Meeting Notes",
...
"content": {
"total_lines": 142,
"matches": [
{
"line_number": 12,
"line": "Discussed architecture decisions.",
"context_before": ["## Design"],
"context_after": ["The team agreed on microservices."]
}
]
}
}content object returns matches and total_lines instead of body. Returns 422 if q parameter is missing.
For large memories, prefer a two-step read: call grep mode first to find each match's line_number, then call again with line_start/line_end to fetch a window of surrounding context.
GET /workspaces/1/memories/1.json?mode=grep&q=TODO{
"content": {
"total_lines": 412,
"matches": [
{ "line_number": 47, "line": "TODO: revisit caching" }
]
}
}GET /workspaces/1/memories/1.json?line_start=40&line_end=55head= or tail= parameter. Head is line_start=1&line_end=N; tail is line_start=(total_lines - N + 1)&line_end=total_lines. Because total_lines is echoed on every response, clients can compute tail without an extra round trip.
Create Memory
Creates a new memory with content.
| Name | Type | Description |
|---|---|---|
| memory[title] | string | Memory title (max 255 characters) |
| memory[content] | string | Memory body (Markdown) |
| memory[source] | string | Source identifier |
| memory[tags] | array | Array of tag strings |
| memory[category] | string | One of decision, discovery, preference, general (defaults to general). See Memory Categories. |
{
"memory": {
"title": "API Documentation",
"content": "# Overview\n\nThis document describes...",
"category": "decision",
"tags": ["docs", "api"]
}
}Update Memory
Updates an existing memory.
| Name | Type | Description |
|---|---|---|
| memory[title] | string | Memory title |
| memory[content] | string | Memory body |
| memory[source] | string | Source identifier |
| memory[tags] | array | Array of tags |
| memory[category] | string | One of decision, discovery, preference, general. See Memory Categories. |
Delete Memory
Deletes a memory and all its versions.
Memory Versions
Create Version
Creates a new version of a memory. Fields default to the parent version values if not provided.
| Name | Type | Description |
|---|---|---|
| version[title] | string | Version title (defaults to parent) |
| version[content] | string | Version body (defaults to parent) |
| version[source] | string | Source identifier (defaults to parent) |
| version[tags] | array | Tags (defaults to parent) |
| version[category] | string | Category (defaults to the parent memory's category). See Memory Categories. |
{
"version": {
"content": "# Updated Content\n\nRevised version..."
}
}{
"id": 5,
"title": "Meeting Notes",
"version": 2,
"source": "manual",
"category": "decision",
"tags": ["meetings", "q1"],
"created_at": "2026-02-04T16:00:00Z",
"updated_at": "2026-02-04T16:00:00Z",
"url": "https://recuerd0.ai/workspaces/1/memories/5",
"content": {
"body": "# Updated Content\n\nRevised version..."
},
"workspace": {
"id": 1,
"name": "Project Alpha",
"url": "https://recuerd0.ai/workspaces/1"
}
}Search
Search Memories
Full-text search across all memories in active workspaces. Supports FTS5 query operators and a grep mode for line-level matching.
| Name | Type | Description | |
|---|---|---|---|
| q | string | required | Search query (3–100 characters) |
| page | integer | Page number (default: 1) | |
| workspace_id | integer | Filter results to a specific workspace | |
| category | string | Filter results to one category. See Memory Categories. | |
| mode | string | Response mode: snippet (default) or grep | |
| context | integer | Lines before and after each match, like grep -C (0–10, default: 0). Only with mode=grep | |
| before | integer | Lines before each match, like grep -B (0–10). Overrides context. Only with mode=grep | |
| after | integer | Lines after each match, like grep -A (0–10). Overrides context. Only with mode=grep |
| Operator | Example | Description |
|---|---|---|
| Term | architecture | Matches documents containing the substring |
| AND | architecture AND design | Both terms must appear |
| OR | meeting OR standup | Either term can appear |
| NOT | design NOT draft | First term must appear, second must not |
| Phrase | "project timeline" | Exact phrase match |
| Column | title:architecture | Search only in title field |
| Column | body:implementation | Search only in body field |
| Grouping | (meeting OR standup) AND notes | Parentheses for precedence |
{
"query": "architecture AND design",
"total_results": 3,
"results": [
{
"id": 1,
"title": "Design Doc",
"version": 1,
"version_label": "v1",
"has_versions": false,
"category": "decision",
"tags": ["design"],
"source": "manual",
"snippet": "Initial architecture overview...",
"workspace": { ... }
}
]
}GET /search.json?q=architecture&mode=grep&context=1
{
"query": "architecture",
"total_results": 2,
"results": [
{
"id": 1,
"title": "Design Doc",
...
"total_lines": 45,
"matches": [
{
"line_number": 12,
"line": "The architecture uses a layered approach.",
"context_before": ["## System Design"],
"context_after": ["Each layer communicates through interfaces."]
}
],
"workspace": { ... }
}
]
}422 with VALIDATION_ERROR.
matches and total_lines instead of snippet. FTS5 finds candidate memories, then line-level matching extracts specific lines with context.