# REST API

Read and write your memories over plain HTTPS, using the same OAuth token your MCP clients already use. No SDK, no API key.

## Base URL

```text
https://api.agentage.io/v1
```

Twelve endpoints under `/v1`: list, create, inspect, and retire memories; list, read, write, edit, and delete notes; search; and export. Every route twins one of the six frozen `memory__*` MCP tools, so field names, defaults, and limits match the tool contract you already know.

> **The vaults noun is deprecated**
>
> `/v1/vaults/*` still resolves, as a deprecated alias of `/v1/memories/*`, sunsetting Sun, 08 Feb 2027 00:00:00 GMT. It is a path alias, not a shape-compat layer: responses already use the new memories field names. Every response on the alias carries `Deprecation: true`, `Sunset: Sun, 08 Feb 2027 00:00:00 GMT`, and `Link: </v1/memories>; rel="successor-version"`.

## Authentication

Send an OAuth 2.1 access token as a bearer header:

```text
Authorization: Bearer <access-token>
```

The token is the same one issued when you connect any MCP client (OAuth 2.1 with PKCE, sign-in at auth.agentage.io). Which memories you see is decided by the token, not by request parameters - a token scoped to one memory lists only that memory. Reads need the `memory:read` scope; writes need `memory:write`.

## Endpoints

Every endpoint, grouped by resource. Click a row to expand its contract - parameters, a curl example, the 200 response, response fields, and error codes.

### Memories

#### `GET /v1/memories`

List the memories your token can see (live)

Returns every memory visible to the presented token. Visibility is decided by the token (its memories claim), never by request parameters. Requires memory:read.

Request:

```bash
curl -s \
  -H "Authorization: Bearer $AGENTAGE_TOKEN" \
  https://api.agentage.io/v1/memories
```

Response:

```json
{
  "memories": [
    { "name": "default", "files": 412, "folders": 37, "sizeBytes": 8388608,
      "updated": "2026-07-06T07:31:02+00:00", "empty": false,
      "rev": "3f1c9a0e8b2d4c5e6f708192a3b4c5d6e7f80912" }
  ]
}
```

| Field | Type | Description |
| --- | --- | --- |
| `memories` | array | One object per visible memory. |
| `memories[].name` | string | Memory slug, 1-64 chars of a-z 0-9 _ -. |
| `memories[].files` | integer | Number of notes in the memory. |
| `memories[].folders` | integer | Number of folders. |
| `memories[].sizeBytes` | integer | Total memory size. |
| `memories[].updated` | string \| null | ISO 8601 last write; null when empty. |
| `memories[].empty` | boolean | True when the memory has no notes. |
| `memories[].rev` | string \| null | HEAD sha. Changes only on write; use as a poll token and If-Match value. |

Errors: `401` missing, invalid, or expired token; `403` missing required scope; `429` rate limit exceeded; `503` auth service unavailable, retry

#### `POST /v1/memories`

Create a memory (live)

Creates a named memory. Idempotent: an existing name returns 200 with the memory; a new name returns 201. `export` is reserved - it addresses the collection-export route, not a memory. Requires memory:write.

| Parameter | Type | Description |
| --- | --- | --- |
| `name` | string, body | Memory slug, 1-64 chars of a-z 0-9 _ -. Required. |

Request:

```bash
curl -s -X POST \
  -H "Authorization: Bearer $AGENTAGE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"work"}' \
  https://api.agentage.io/v1/memories
```

Response:

```json
{ "name": "work", "files": 0, "folders": 0, "sizeBytes": 0,
  "updated": null, "empty": true, "rev": null }
```

| Field | Type | Description |
| --- | --- | --- |
| `name` | string | The created (or existing) slug. |
| `...` |  | Other fields as in memories[] above. |

Errors: `400` invalid or reserved name; `401` missing, invalid, or expired token; `403` missing required scope; `409` at the per-account memory ceiling; `429` rate limit exceeded; `503` auth service unavailable, retry

#### `GET /v1/memories/{memory}`

Memory stats (live)

Stats for one memory: counts, size, last activity, and its current rev.

| Parameter | Type | Description |
| --- | --- | --- |
| `memory` | string, path | Memory slug. Must be granted by the token. |

Request:

```bash
curl -s -H "Authorization: Bearer $AGENTAGE_TOKEN" \
  https://api.agentage.io/v1/memories/default
```

Response:

```json
{ "name": "default", "files": 412, "folders": 37, "sizeBytes": 8388608,
  "updated": "2026-07-06T07:31:02+00:00", "empty": false,
  "rev": "3f1c9a0e8b2d4c5e6f708192a3b4c5d6e7f80912" }
```

| Field | Type | Description |
| --- | --- | --- |
| `rev` | string \| null | HEAD sha - poll it, or send it back as If-Match on a write. |
| `...` |  | Other fields as in memories[] above. |

Errors: `401` missing, invalid, or expired token; `403` memory not granted to this token; `404` no such memory; `429` rate limit exceeded; `503` auth service unavailable, retry

#### `DELETE /v1/memories/{memory}`

Retire a memory (live)

Deletes a memory. Recoverable: the store tombstones it rather than destroying it. Requires memory:write.

| Parameter | Type | Description |
| --- | --- | --- |
| `memory` | string, path | Memory slug. |
| `If-Match` | string, header | Optional. The memory's rev; a stale value fails the write with 409. |

Request:

```bash
curl -s -X DELETE \
  -H "Authorization: Bearer $AGENTAGE_TOKEN" \
  -H "If-Match: 3f1c9a0e8b2d4c5e6f708192a3b4c5d6e7f80912" \
  https://api.agentage.io/v1/memories/work
```

Response:

```json
{ "name": "work", "deleted": true }
```

| Field | Type | Description |
| --- | --- | --- |
| `name` | string | The retired slug. |
| `deleted` | boolean | Always true on success. |

Errors: `401` missing, invalid, or expired token; `403` memory not granted to this token; `404` no such memory; `409` If-Match did not match the current rev; `429` rate limit exceeded; `503` auth service unavailable, retry

### Notes

#### `GET /v1/memories/{memory}/notes`

List notes and folders (live)

Paginated listing of one folder level, twin of memory__list. Mixes file and folder entries; use `type` to filter to one kind.

| Parameter | Type | Description |
| --- | --- | --- |
| `memory` | string, path | Memory slug. |
| `folder` | string, query | Folder to browse. Optional, default root. |
| `depth` | integer \| "all", query | Levels below the browsed folder, minimum 1. Default 2. |
| `type` | string, query | all \| file \| folder. Default all. |
| `fields` | string, query | 'path' trims file entries to type + path only. Default full. |
| `tags` | string[], query | AND-matched. Repeat the param or comma-separate. |
| `sort` | string, query | name \| updated. Default name. |
| `limit` | integer, query | 1-500, default 200. |
| `cursor` | string, query | Opaque pagination cursor. |

Request:

```bash
curl -s -H "Authorization: Bearer $AGENTAGE_TOKEN" \
  "https://api.agentage.io/v1/memories/default/notes?folder=work&limit=50"
```

Response:

```json
{
  "folder": "work",
  "entries": [
    { "type": "file", "path": "work/plan.md", "title": "Plan", "tags": ["work"],
      "excerpt": "Q3 focus is...", "sizeBytes": 2048,
      "updated": "2026-07-05T21:47:55+00:00" },
    { "type": "folder", "path": "work/archive", "files": 6 }
  ],
  "files": 7,
  "truncated": false
}
```

| Field | Type | Description |
| --- | --- | --- |
| `folder` | string | The browsed folder, echoed back. |
| `entries` | array | One entry per file or subfolder; shape depends on type. |
| `entries[].type` | string | "file" or "folder". |
| `entries[].path` | string | POSIX path inside the memory. |
| `entries[].title` | string | First heading or filename. File entries only. |
| `entries[].tags` | string[] | Frontmatter tags. File entries only. |
| `entries[].excerpt` | string | Unconditional preview, not query-anchored. File entries only. |
| `entries[].sizeBytes` | integer | Note size. File entries only. |
| `entries[].updated` | string \| null | ISO 8601, last write. File entries only. |
| `entries[].files` | integer | Recursive file count. Folder entries only. |
| `files` | integer | File count for this listing. |
| `truncated` | boolean | True when more entries exist beyond limit. |
| `nextCursor` | string | Pass back as cursor for the next page. Omitted when the listing is exhausted. |

Errors: `400` invalid cursor or query value; `401` missing, invalid, or expired token; `403` memory not granted; `404` no such memory; `429` rate limit exceeded; `503` auth service unavailable, retry

#### `GET /v1/memories/{memory}/notes/{path}`

Read a note (live)

Full note by path: frontmatter, markdown body, and metadata, twin of memory__read. Send `Accept: text/markdown` to get the file exactly as stored instead of the JSON envelope.

| Parameter | Type | Description |
| --- | --- | --- |
| `memory` | string, path | Memory slug. |
| `path` | string, path | POSIX .md path, URL-encoded. |
| `Accept` | string, header | text/markdown returns the raw file. Optional, default application/json. |

Request:

```bash
curl -s -H "Authorization: Bearer $AGENTAGE_TOKEN" \
  https://api.agentage.io/v1/memories/default/notes/work%2Fplan.md
```

Response:

```json
{
  "path": "work/plan.md", "title": "Plan",
  "frontmatter": { "tags": ["work"] },
  "body": "# Plan\n\nQ3 focus is...",
  "tags": ["work"], "sizeBytes": 2048,
  "updated": "2026-07-05T21:47:55+00:00"
}
```

| Field | Type | Description |
| --- | --- | --- |
| `path` | string | POSIX .md path inside the memory. |
| `title` | string | First heading or filename. |
| `frontmatter` | object | Parsed YAML frontmatter. |
| `body` | string | Markdown body. |
| `tags` | string[] | Frontmatter tags. |
| `sizeBytes` | integer | Note size. |
| `updated` | string | ISO 8601, last write. |

Errors: `401` missing, invalid, or expired token; `403` memory not granted; `404` no such note; `429` rate limit exceeded; `503` auth service unavailable, retry

#### `PUT /v1/memories/{memory}/notes/{path}`

Write a note (create or replace) (live)

Full write, twin of memory__write. Send JSON with a `body` field, or post raw markdown with `Content-Type: text/markdown`. Requires memory:write.

| Parameter | Type | Description |
| --- | --- | --- |
| `memory` | string, path | Memory slug. |
| `path` | string, path | Target .md path, URL-encoded. |
| `If-Match` | string, header | Optional. The memory's rev; a stale value fails the write with 409. |
| `body` | string, body | Markdown body. Required in the JSON form. |
| `frontmatter` | object, body | Optional frontmatter. |

Request:

```bash
curl -s -X PUT \
  -H "Authorization: Bearer $AGENTAGE_TOKEN" \
  -H "Content-Type: application/json" \
  -H "If-Match: 3f1c9a0e8b2d4c5e6f708192a3b4c5d6e7f80912" \
  -d '{"body":"# Plan\n..."}' \
  https://api.agentage.io/v1/memories/default/notes/work%2Fplan.md
```

Response:

```json
{ "path": "work/plan.md", "rev": "9a0e8b2d4c5e6f708192a3b4c5d6e7f80912ab3f",
  "updated": "2026-07-06T08:00:00+00:00" }
```

| Field | Type | Description |
| --- | --- | --- |
| `path` | string | Written path. |
| `rev` | string | The memory's new HEAD sha - use it as the next If-Match. |
| `updated` | string | ISO 8601 commit time. |

Errors: `400` missing body, or content refused as a credential; `401` missing, invalid, or expired token; `403` memory not granted; `404` no such memory; `409` If-Match did not match the current rev; `429` rate limit exceeded; `503` auth service unavailable, retry

#### `PATCH /v1/memories/{memory}/notes/{path}`

Edit a note (live)

Partial update, twin of memory__edit: replace, append, or a targeted string replacement. Requires memory:write.

| Parameter | Type | Description |
| --- | --- | --- |
| `memory` | string, path | Memory slug. |
| `path` | string, path | Note path. |
| `If-Match` | string, header | Optional. The memory's rev; a stale value fails the write with 409. |
| `mode` | string, body | replace \| append \| str_replace. Default replace. |
| `body` | string, body | New or appended content. |
| `frontmatter` | object, body | Shallow-merged; cannot remove a key. |
| `old_str` | string, body | Required for str_replace; must match exactly one place. |
| `new_str` | string, body | Omit to delete old_str. |

Request:

```bash
curl -s -X PATCH \
  -H "Authorization: Bearer $AGENTAGE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"mode":"append","body":"\n- new item"}' \
  https://api.agentage.io/v1/memories/default/notes/work%2Fplan.md
```

Response:

```json
{ "path": "work/plan.md", "rev": "0e8b2d4c5e6f708192a3b4c5d6e7f80912ab3f9a",
  "updated": "2026-07-06T08:01:00+00:00" }
```

| Field | Type | Description |
| --- | --- | --- |
| `path` | string | Edited path. |
| `rev` | string | The memory's new HEAD sha - use it as the next If-Match. |
| `updated` | string | ISO 8601 commit time. |

Errors: `400` invalid mode/arguments, no unique str_replace match, or refused content; `401` missing, invalid, or expired token; `403` memory not granted; `404` no such note; `409` If-Match did not match the current rev; `429` rate limit exceeded; `503` auth service unavailable, retry

#### `DELETE /v1/memories/{memory}/notes/{path}`

Delete a note (recoverable) (live)

Soft delete, twin of memory__delete: the note is tombstoned in git history, not destroyed. Requires memory:write.

| Parameter | Type | Description |
| --- | --- | --- |
| `memory` | string, path | Memory slug. |
| `path` | string, path | Note path. |
| `If-Match` | string, header | Optional. The memory's rev; a stale value fails the write with 409. |

Request:

```bash
curl -s -X DELETE \
  -H "Authorization: Bearer $AGENTAGE_TOKEN" \
  https://api.agentage.io/v1/memories/default/notes/work%2Fold.md
```

Response:

```json
{ "path": "work/old.md", "deleted": true }
```

| Field | Type | Description |
| --- | --- | --- |
| `deleted` | boolean | Always true on success. |

Errors: `401` missing, invalid, or expired token; `403` memory not granted; `404` no such note; `409` If-Match did not match the current rev; `429` rate limit exceeded; `503` auth service unavailable, retry

### Search & export

#### `GET /v1/memories/{memory}/search`

Search notes (live)

Lexical search over the memory (git-native, literal keyword matching), ranked by match count, twin of memory__search. Returns paths and snippets, never full bodies.

| Parameter | Type | Description |
| --- | --- | --- |
| `memory` | string, path | Memory slug. |
| `q` | string, query | Search query. Required. |
| `folder` | string, query | Scope to a folder. Optional. |
| `tags` | string[], query | Scope to notes with these tags. |
| `limit` | integer, query | 1-50, default 20. |
| `cursor` | string, query | Pagination cursor. |

Request:

```bash
curl -s -H "Authorization: Bearer $AGENTAGE_TOKEN" \
  "https://api.agentage.io/v1/memories/default/search?q=roadmap"
```

Response:

```json
{
  "results": [
    { "path": "work/plan.md", "title": "Plan",
      "snippet": "...the Q3 roadmap is...", "score": 3,
      "updated": "2026-07-05T21:47:55+00:00" }
  ]
}
```

| Field | Type | Description |
| --- | --- | --- |
| `results` | array | Ranked matches. |
| `results[].snippet` | string | Window around the first match. |
| `results[].score` | integer | Match count, not a relevance percentage. |
| `nextCursor` | string | Next page cursor. Omitted when exhausted. |

Errors: `400` missing q; `401` missing, invalid, or expired token; `403` memory not granted; `404` no such memory; `429` rate limit exceeded; `503` auth service unavailable, retry

#### `GET /v1/memories/{memory}/export`

Export one memory as a git bundle (live)

Streams a cloneable git bundle of one memory: full history, plain markdown, yours. Content-Type application/x-git-bundle.

| Parameter | Type | Description |
| --- | --- | --- |
| `memory` | string, path | Memory slug. |

Request:

```bash
curl -s -H "Authorization: Bearer $AGENTAGE_TOKEN" \
  -o memory.bundle \
  https://api.agentage.io/v1/memories/default/export
git clone memory.bundle my-memory
```

Response:

```json
(binary git bundle stream)
```

| Field | Type | Description |
| --- | --- | --- |
| `-` | application/x-git-bundle | Attachment; clone it with plain git. |

Errors: `401` missing, invalid, or expired token; `403` memory not granted; `404` no such memory, or it has no history; `429` rate limit exceeded; `503` auth service unavailable, retry

#### `GET /v1/memories/export`

Export every visible memory as one zip (live)

Streams a zip of every memory the token can see, one `<memory>/<path>` markdown file per note, frontmatter included. `export` is reserved as a memory name so this route never collides with a real memory.

Request:

```bash
curl -s -H "Authorization: Bearer $AGENTAGE_TOKEN" \
  -o memories.zip \
  https://api.agentage.io/v1/memories/export
```

Response:

```json
(binary zip stream)
```

| Field | Type | Description |
| --- | --- | --- |
| `-` | application/zip | <memory>/<path> markdown files, frontmatter included. |

Errors: `401` missing, invalid, or expired token; `403` missing required scope; `429` rate limit exceeded; `503` auth service unavailable, retry

## Writes and concurrency

Every write (`POST`/`DELETE` on memories, `PUT`/`PATCH`/`DELETE` on notes) accepts an optional `If-Match: <rev>` header for optimistic concurrency. Send the `rev` you last read; a stale value fails the write with `409 CONFLICT` instead of silently overwriting someone else's change.

`rev` is the memory's HEAD sha. Read it from `GET /v1/memories/{memory}`, or from the `rev` field every write returns - chain writes without a re-fetch.

## Errors

Every non-2xx response carries the same JSON envelope. `code` is a stable, machine-readable string you can switch on; `message` is a human-readable hint that may change.

```json
{ "error": { "code": "UNAUTHENTICATED", "message": "missing bearer token" } }
```

Codes: `UNAUTHENTICATED` (401), `FORBIDDEN` (403), `NOT_FOUND` (404), `BAD_REQUEST` (400), `CONFLICT` (409), `RATE_LIMITED` (429), `UPSTREAM_UNAVAILABLE` (503).

## Limits and versioning

- Reads need the `memory:read` scope; writes need `memory:write`.
- Rate limited to 60 requests per minute per IP.
- Rate-limit responses carry IETF draft-7 headers `ratelimit` and `ratelimit-policy` (not `X-RateLimit-*`); a 429 also sends `Retry-After`.
- The `/v1` contract is frozen: fields are only ever added, never renamed or removed. Breaking changes would ship as `/v2`.

## What this API is not

- Not a sync channel - memory contents sync over git (Obsidian plugin, CLI).
- No API keys - OAuth 2.1 bearer only.
