# Local API

A small JSON HTTP API your machine serves on the loopback interface - health, the six memory verbs, git-sync control, and a local MCP endpoint. For scripts and tools on this machine; anything remote uses the cloud MCP server instead.

## The base URL

```text
http://127.0.0.1:4243
```

The CLI's background daemon serves this API. It autostarts on the first memory verb, or start it explicitly with `agentage daemon start`. Every route below is plain JSON over HTTP.

> **Loopback only, no auth**
>
> The daemon binds to `127.0.0.1` and there is **no authentication** on the socket - it trusts every process on your machine and is never exposed to the network. Use it for local scripts and tools. For anything remote, use the cloud [MCP server](/docs/mcp-server) and [REST API](/docs/rest-api), which are OAuth-protected. Change the port with `AGENTAGE_DAEMON_PORT`.

## Endpoints

Every route, grouped by resource. Click a row to expand its contract - parameters, a curl example, the JSON response, response fields, and errors. The memory routes mirror the [MCP tools](/docs/mcp-tools) one-to-one; the request body carries the same parameters.

### Health

#### `GET /api/health`

Liveness, version and uptime (live)

Cheap liveness probe. Returns the running CLI version, the daemon pid, its uptime and how many memory requests it has served. Use it to check whether the daemon is up before scripting against it.

Request:

```bash
curl -s http://127.0.0.1:4243/api/health
```

Response:

```json
{
  "ok": true,
  "version": "0.1.0",
  "pid": 51234,
  "uptime": 42,
  "served": 7
}
```

| Field | Type | Description |
| --- | --- | --- |
| `ok` | boolean | Always true when the daemon answers. |
| `version` | string | Running CLI version. |
| `pid` | integer | Daemon process id. |
| `uptime` | integer | Seconds since the daemon started. |
| `served` | integer | Memory requests served so far. |

Errors: `ECONNREFUSED` the daemon is not running - start it with `agentage daemon start`

### Memory

#### `POST /api/memory/search`

Search a vault (git grep) (live)

Literal substring search over a vault, ranked by match count. Returns paths and snippets, never full bodies. Mirrors the memory__search tool.

| Parameter | Type | Description |
| --- | --- | --- |
| `query` | string, body | Search text. Required. |
| `opts.vault` | string, body | Target vault. Optional. |
| `opts.folder` | string, body | Scope to a folder. Optional. |
| `opts.limit` | integer, body | Max hits, default 20. |

Request:

```bash
curl -s -X POST http://127.0.0.1:4243/api/memory/search \
  -H "Content-Type: application/json" \
  -d '{"query":"postgres","opts":{"vault":"notes","limit":5}}'
```

Response:

```json
{
  "results": [
    { "path": "@notes/welcome.md", "title": "welcome",
      "snippet": "# Welcome We use Postgres for full-text search.",
      "score": 1, "updated": "2026-07-06T14:11:31+00:00" }
  ]
}
```

| Field | Type | Description |
| --- | --- | --- |
| `results` | array | Ranked matches. |
| `results[].path` | string | Document path; @<vault>/ prefixed when scoped to a vault. |
| `results[].snippet` | string | Match context. |
| `results[].score` | integer | Match count - how many times the query hits the note. |
| `nextCursor` | string, optional | Present only when more pages exist; pass back as opts.cursor for the next page. |

Errors: `400` invalid JSON body; `404` unknown verb

#### `POST /api/memory/read`

Read a document (live)

Full document by path: frontmatter, markdown body and metadata. Bodies over 64 KB are clamped. Mirrors memory__read.

| Parameter | Type | Description |
| --- | --- | --- |
| `ref` | string, body | @<vault>/<path>, or <path> with opts.vault. Required. |
| `opts.vault` | string, body | Target vault. Optional. |

Request:

```bash
curl -s -X POST http://127.0.0.1:4243/api/memory/read \
  -H "Content-Type: application/json" \
  -d '{"ref":"@notes/welcome.md"}'
```

Response:

```json
{
  "path": "welcome.md",
  "title": "welcome",
  "frontmatter": {},
  "body": "# Welcome\nWe use Postgres for full-text search.\n",
  "tags": [],
  "updated": "2026-07-06T14:11:31+00:00",
  "deleted": false
}
```

| Field | Type | Description |
| --- | --- | --- |
| `path` | string | Resolved document path, vault-relative (no @ prefix). |
| `title` | string | Filename stem. |
| `frontmatter` | object | Parsed YAML frontmatter. |
| `body` | string | Markdown body. |
| `tags` | string[] | Frontmatter tags. |
| `updated` | string | ISO 8601 last write. |
| `deleted` | boolean | True for a tombstoned document. |

Errors: `400` not found, or invalid body

#### `POST /api/memory/write`

Create or overwrite a document (live)

Idempotent full write - creates or replaces the document and commits. The engine refuses to store obvious secrets. Mirrors memory__write.

| Parameter | Type | Description |
| --- | --- | --- |
| `ref` | string, body | Target document. Required. |
| `body` | string, body | Markdown body. Required. |
| `opts.vault` | string, body | Target vault. Optional. |
| `opts.frontmatter` | object, body | YAML frontmatter to set. Optional. |

Request:

```bash
curl -s -X POST http://127.0.0.1:4243/api/memory/write \
  -H "Content-Type: application/json" \
  -d '{"ref":"work/plan.md","body":"Q3 focus is search.","opts":{"vault":"notes","frontmatter":{"tags":["work"]}}}'
```

Response:

```json
{
  "path": "@notes/work/plan.md",
  "rev": "8e0670daa3bef7cdd58152b70092e3da553445b4",
  "updated": "2026-07-06T14:37:07.262Z"
}
```

| Field | Type | Description |
| --- | --- | --- |
| `path` | string | The document path that was written, echoed with its @<vault>/ prefix when the request was vault-scoped. |
| `rev` | string | Git commit hash of this write. |
| `updated` | string | ISO 8601 commit time. |

Errors: `400` invalid body, or a refused secret

#### `POST /api/memory/edit`

Edit a document (live)

Targeted edit: str_replace (op.old_str / op.new_str), or replace/append the body (op.mode + op.body). Mirrors memory__edit.

| Parameter | Type | Description |
| --- | --- | --- |
| `ref` | string, body | Target document. Required. |
| `op.mode` | string, body | replace \| append \| str_replace. |
| `op.body` | string, body | New or appended content. |
| `op.old_str / op.new_str` | string, body | Exact, unique substring to replace, and its replacement. |

Request:

```bash
curl -s -X POST http://127.0.0.1:4243/api/memory/edit \
  -H "Content-Type: application/json" \
  -d '{"ref":"@notes/welcome.md","op":{"mode":"str_replace","old_str":"Postgres","new_str":"SQLite"}}'
```

Response:

```json
{
  "path": "@notes/welcome.md",
  "rev": "2abbd3203f725b8373e56194df938fdc6b887bab",
  "updated": "2026-07-06T14:12:22.931Z"
}
```

| Field | Type | Description |
| --- | --- | --- |
| `path` | string | The document path that was written, echoed with its @<vault>/ prefix when the request was vault-scoped. |
| `rev` | string | Git commit hash of this write. |
| `updated` | string | ISO 8601 commit time. |

Errors: `400` no match for old_str, or not found

#### `POST /api/memory/list`

List the folder tree (live)

Browse a vault as a folder tree (two levels deep). Paths are @<vault>/ prefixed when scoped to a vault. Mirrors memory__list.

| Parameter | Type | Description |
| --- | --- | --- |
| `folder` | string, body | Folder prefix. Optional. |
| `opts.vault` | string, body | Target vault. Optional. |

Request:

```bash
curl -s -X POST http://127.0.0.1:4243/api/memory/list \
  -H "Content-Type: application/json" \
  -d '{"opts":{"vault":"notes"}}'
```

Response:

```json
{
  "folder": "@notes",
  "entries": [
    { "type": "folder", "path": "@notes/work", "files": 2,
      "entries": [
        { "type": "folder", "path": "@notes/work/tasks", "files": 1 },
        { "type": "file", "path": "@notes/work/plan.md",
          "title": "plan", "updated": "2026-07-06T14:37:07+00:00" }
      ] },
    { "type": "file", "path": "@notes/welcome.md",
      "title": "welcome", "updated": "2026-07-06T14:37:07+00:00" }
  ],
  "truncated": false,
  "files": 3
}
```

| Field | Type | Description |
| --- | --- | --- |
| `folder` | string | The listed folder. |
| `entries` | array | File and folder nodes, tree order. |
| `entries[].type` | string | "file" or "folder". |
| `entries[].path` | string | Node path. |
| `entries[].files` | integer | Folder nodes: file count under the folder. |
| `entries[].entries` | array, optional | Folder nodes: nested children, down to two levels deep in total. |
| `entries[].truncated` | boolean, optional | Folder nodes: true when its children were cut at the per-folder cap. |
| `files` | integer | Total files under the folder. |

Errors: `400` invalid body

#### `POST /api/memory/delete`

Delete a document (recoverable) (live)

Removes the document and commits the deletion, so it stays recoverable from git history. Mirrors memory__delete.

| Parameter | Type | Description |
| --- | --- | --- |
| `ref` | string, body | Target document. Required. |
| `opts.vault` | string, body | Target vault. Optional. |

Request:

```bash
curl -s -X POST http://127.0.0.1:4243/api/memory/delete \
  -H "Content-Type: application/json" \
  -d '{"ref":"@notes/old.md"}'
```

Response:

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

| Field | Type | Description |
| --- | --- | --- |
| `path` | string | The deleted document path. |
| `deleted` | boolean | Always true on success (recoverable from git history). |

Errors: `400` not found

### Sync

#### `GET /api/sync/status`

Per-vault git-sync state (live)

The current state of every git-backed vault: its remote, interval, whether a cycle is running, and the last run and result. Empty when no vault has a git remote.

Request:

```bash
curl -s http://127.0.0.1:4243/api/sync/status
```

Response:

```json
{
  "vaults": [
    { "vault": "work",
      "remote": "git@github.com:you/memory.git",
      "intervalSeconds": 300,
      "running": false }
  ]
}
```

| Field | Type | Description |
| --- | --- | --- |
| `vaults` | array | One entry per git-backed vault. |
| `vaults[].vault` | string | Vault name. |
| `vaults[].remote` | string | Git remote URL. |
| `vaults[].intervalSeconds` | integer | Auto-sync interval; 0 = manual only. |
| `vaults[].running` | boolean | True while a cycle is in flight. |
| `vaults[].lastRun` | string | ISO 8601 of the last cycle. Optional. |
| `vaults[].lastResult` | object | Summary of the last cycle (ok, pushed, committed, conflicts). Optional. |

#### `POST /api/sync/run`

Sync one vault now (live)

Forces a commit + push + pull-rebase for a single vault, regardless of its interval. Conflicts keep both sides: the remote copy is written as <file>.conflict.md.

| Parameter | Type | Description |
| --- | --- | --- |
| `vault` | string, body | Vault to sync. Required. |

Request:

```bash
curl -s -X POST http://127.0.0.1:4243/api/sync/run \
  -H "Content-Type: application/json" \
  -d '{"vault":"work"}'
```

Response:

```json
{
  "vault": "work",
  "remote": "git@github.com:you/memory.git",
  "ok": true,
  "committed": true,
  "pushed": true,
  "conflicts": []
}
```

| Field | Type | Description |
| --- | --- | --- |
| `ok` | boolean | False when the cycle failed. |
| `committed` | boolean | A sync commit was made. |
| `pushed` | boolean | Local commits were pushed. |
| `conflicts` | string[] | Paths written as <file>.conflict.md. |
| `reason / error` | string | Failure kind and message when ok is false (e.g. "unreachable"). |

Errors: `400` vault is required, or no sync origin configured

### MCP

#### `POST /mcp`

Local MCP over Streamable HTTP (live)

The same frozen six memory__* tools as the cloud endpoint, served locally as stateless Streamable HTTP JSON-RPC. GET and DELETE return 405 (stateless: POST only). See the MCP tools reference for the tool contracts.

| Parameter | Type | Description |
| --- | --- | --- |
| `(body)` | JSON-RPC 2.0 | A JSON-RPC request, e.g. tools/list or tools/call. |

Request:

```bash
curl -s -X POST http://127.0.0.1:4243/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
```

Response:

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "tools": [
      { "name": "memory__search" },
      { "name": "memory__read" },
      { "name": "memory__write" },
      { "name": "memory__edit" },
      { "name": "memory__list" },
      { "name": "memory__delete" }
    ]
  }
}
```

| Field | Type | Description |
| --- | --- | --- |
| `result.tools` | array | The six memory__* tool contracts (names, schemas, annotations). |

Errors: `405` GET or DELETE - the endpoint is stateless (POST only); `500` internal error

## What this API is not

- Not remote - it lives on `127.0.0.1` only; never port-forward or proxy it to the internet.
- Not authenticated - there are no tokens or keys; access is machine-local trust.
- Not the way to connect an AI - point MCP clients at the stdio command or the local endpoint `http://127.0.0.1:4243/mcp` (see the [CLI guide](/docs/cli)). AI outside this machine uses the cloud [MCP server](/docs/mcp-server).
