# How the /api/v1 Web API Provides Read-Only Access to Memory Data in AI Memory

> Explore how the /api/v1 Web API in akitaonrails/ai-memory offers secure read-only access to memory data for third-party front-ends using token authentication and ETags.

- Repository: [Fabio Akita/ai-memory](https://github.com/akitaonrails/ai-memory)
- Tags: api-reference
- Published: 2026-08-19

---

**The `/api/v1` endpoint in the AI Memory repository is a purpose-built JSON API that exposes knowledge base data through strictly read-only operations, utilizing bearer-token authentication, SHA-256 ETags, and architectural isolation from write interfaces to serve third-party front-ends safely.**

The [akitaonrails/ai-memory](https://github.com/akitaonrails/ai-memory) repository implements a Rust-based knowledge management system where the web API surface at `/api/v1` serves as the primary interface for external tools to query memory data. This API is intentionally designed as a read-only layer, ensuring that external consumers can retrieve workspaces, projects, pages, and search results without risk of modifying the underlying knowledge base.

## Read-Only by Architectural Design

The read-only guarantee stems from the API's implementation in [`crates/ai-memory-web/src/routes/api.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-web/src/routes/api.rs), where every handler operates exclusively on the `WebState` reader and wiki components.

### No Write Operations in Router Handlers

Unlike administrative interfaces, the `/api/v1` route handlers never invoke writer methods. The router only accesses the `reader` field from the shared application state, which provides methods such as `list_workspaces()`, `list_projects()`, and `search_pages()` defined in [`crates/ai-memory-store/src/reader.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-store/src/reader.rs). This architectural constraint means the API physically cannot execute creates, updates, or deletes regardless of request payload, as the `WebState` passed to handlers lacks write-capable dependencies.

### Separation from Mutation Interfaces

Any mutation operations—including page creation, editing, deletion, linting, or consolidation—are routed exclusively through `/admin/*` endpoints (CLI access) or MCP (Model Context Protocol) tools. This strict separation ensures that the public web API remains a read-only surface by construction, with write functionality isolated behind additional authorization layers.

## Authentication and Row-Level Security

Requests to `/api/v1` undergo the same bearer-token and host-allowlist middleware that protects MCP, hook, and admin routes, as configured in [`crates/ai-memory-cli/src/commands/serve.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-cli/src/commands/serve.rs).

### Owner Filtering with `owner_filter_for`

After authentication, the helper function `owner_filter_for` (defined in [`crates/ai-memory-web/src/routes/api.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-web/src/routes/api.rs)) derives an `OwnerFilter` that restricts query results to rows owned by the authenticated caller plus explicitly shared rows. This prevents cross-tenant data leakage while allowing collaborative access where authorized.

### Hand-off Redaction

Sensitive hand-off data undergoes additional filtering unless the caller possesses root operator privileges or specific authorization permissions. This ensures that automated observations and internal session notes remain private even within shared workspaces.

## Cache Optimization and Performance

The API implements sophisticated HTTP caching to minimize redundant database queries and improve front-end responsiveness.

### ETag and Conditional Requests

For page retrieval endpoints, the system generates a SHA-256 `ETag` based on content hash. Clients can include this value in `If-None-Match` headers to receive `304 Not Modified` responses when content remains unchanged, reducing bandwidth consumption for frequently accessed documentation.

### Cache-Control Directives

- **Public data endpoints** (workspaces, projects, pages, global search) return `Cache-Control: private, max-age=N`, allowing browser caching for a configured duration.
- **Identity-dependent endpoints** (hand-offs, briefings, session listings) return `Cache-Control: private, no-store` to prevent cached responses from leaking across user sessions.

## Uniform Schema and Scoping Rules

All endpoints return consistent JSON structures defined by types such as `WorkspaceSummary`, `PageSummary`, `ApiPage`, and `ApiSearchHit`. Errors follow a uniform format: `{ "error": "human-readable message" }` with appropriate HTTP status codes.

### Global vs. Scoped Queries

- **Global operations** (`GET /api/v1/search`) operate across all accessible workspaces.
- **Scoped queries** require both `workspace` and `project` parameters; partial scopes return `400 Bad Request`.
- **Multi-scope search** (`POST /api/v1/search`) accepts up to 25 discrete scope objects in the request body, enabling complex cross-project queries while maintaining performance boundaries.

## Client Integration Examples

### JavaScript Client Implementation

```javascript
const basePath = document.querySelector('meta[name="ai-memory-base-path"]')
                ?.getAttribute('content') ?? '';
const API = `${location.origin}${basePath}/api/v1`;
const TOKEN = localStorage.getItem('ai-memory-token');

async function apiGet(path, params) {
  const url = new URL(`${API}${path}`, location.origin);
  if (params) Object.entries(params).forEach(([k, v]) =>
    v != null && url.searchParams.set(k, v));
  const resp = await fetch(url, {
    headers: {
      Accept: 'application/json',
      ...(TOKEN ? { Authorization: `Bearer ${TOKEN}` } : {})
    }
  });
  if (!resp.ok) {
    const { error } = await resp.json().catch(() => ({ error: resp.statusText }));
    throw new Error(`${resp.status}: ${error}`);
  }
  return resp.json();
}

// Read workspace overview
const overview = await apiGet(
  '/workspaces/default/projects/ai-memory/overview',
  { limit: 10 }
);
console.log('Recent activity:', overview.briefing.activity_7d);

```

### Command-Line Usage

```bash

# List accessible workspaces

curl -fsS "http://127.0.0.1:49374/api/v1/workspaces" \
     -H "Authorization: Bearer $TOKEN" | jq

# Retrieve page with ETag inspection

curl -I "http://127.0.0.1:49374/api/v1/workspaces/default/projects/ai-memory/pages/README.md" \
     -H "Authorization: Bearer $TOKEN"

# Multi-scope POST search (maximum 25 scopes)

curl -fsS -X POST "http://127.0.0.1:49374/api/v1/search" \
     -H "Authorization: Bearer $TOKEN" \
     -H "Content-Type: application/json" \
     -d '{"q":"memory","scopes":[{"workspace":"default","project":"ai-memory"}],"limit":10}' | jq

```

## Summary

- **Architectural read-only guarantee**: The `/api/v1` surface in [`crates/ai-memory-web/src/routes/api.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-web/src/routes/api.rs) only accesses `WebState.reader`, preventing any mutation operations by design.
- **Security middleware**: Bearer-token authentication combined with `owner_filter_for` ensures row-level security and prevents cross-tenant data access.
- **Performance optimization**: SHA-256 `ETag` headers and granular `Cache-Control` directives optimize bandwidth for public data while protecting private session information.
- **Flexible scoping**: Supports both global search across workspaces and precise multi-scope queries (up to 25 projects) via GET and POST endpoints.
- **Strict separation of concerns**: Write operations remain isolated to `/admin/*` routes and MCP tools, ensuring the web API cannot modify the knowledge base even if authentication is compromised.

## Frequently Asked Questions

### How does the API prevent accidental write operations?

The API enforces read-only access architecturally by only exposing the `reader` and `wiki` components of `WebState` to route handlers in [`crates/ai-memory-web/src/routes/api.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-web/src/routes/api.rs). Because the handler functions never receive access to write-capable stores, the code physically cannot execute INSERT, UPDATE, or DELETE operations regardless of request method or payload content. All mutations must route through separate `/admin/*` endpoints or MCP tool interfaces that require distinct authorization.

### What authentication is required to access memory data?

All `/api/v1` endpoints require a bearer token passed in the `Authorization` header, processed by the same middleware stack used for MCP and administrative routes. After validation, the `owner_filter_for` helper function restricts queries to resources owned by the authenticated user or explicitly shared with them. Tokens can be generated via the CLI using `ai-memory generate-auth-token` for programmatic access.

### How does caching work for sensitive vs. public data?

Public data endpoints—such as workspace listings, project pages, and global search—attach `Cache-Control: private, max-age=N` headers and SHA-256 `ETag` values to enable browser caching and conditional requests. Identity-dependent endpoints including hand-offs and session observations use `Cache-Control: private, no-store` to ensure browsers never cache user-specific data, preventing potential information leakage across browser sessions or shared devices.

### Can I search across multiple projects simultaneously?

Yes. While scoped queries require both `workspace` and `project` parameters for single-project access, the `POST /api/v1/search` endpoint accepts an array of up to 25 scope objects in the request body. Each scope specifies a workspace-project pair, enabling complex cross-project research queries while maintaining the architectural guarantee that the search remains strictly read-only and respects row-level security filters for each project in the scope list.