# Gateway HTTP API

> TdaiGateway routes: GET /health, POST /recall, /capture, /search/memories, /search/conversations, /session/end, /seed — request and response fields, auth exceptions, and error envelope.

- Repository: TencentCloud/TencentDB-Agent-Memory
- GitHub: https://github.com/TencentCloud/TencentDB-Agent-Memory
- Human docs: https://grok-wiki.com/public/docs/tencentcloud-tencentdb-agent-memory-5a33bbf5540a
- Complete Markdown: https://grok-wiki.com/public/docs/tencentcloud-tencentdb-agent-memory-5a33bbf5540a/llms-full.txt

## Source Files

- `src/gateway/server.ts`
- `src/gateway/types.ts`
- `src/gateway/config.ts`
- `src/core/tdai-core.ts`
- `hermes-plugin/memory/memory_tencentdb/client.py`

---

---
title: "Gateway HTTP API"
description: "TdaiGateway routes: GET /health, POST /recall, /capture, /search/memories, /search/conversations, /session/end, /seed — request and response fields, auth exceptions, and error envelope."
---

`TdaiGateway` (`src/gateway/server.ts`) is a Node.js native `http` server that exposes host-neutral `TdaiCore` capabilities over HTTP for Hermes and other sidecar clients. It does not use Express or Fastify. Default bind is `127.0.0.1:8420`. All bodies are JSON; all successful responses are JSON with `Content-Type: application/json`.

## Endpoint inventory

| Method | Path | Auth | Core call | Purpose |
|--------|------|------|-----------|---------|
| `GET` | `/health` | Never required | Store readiness | Liveness / readiness probe |
| `POST` | `/recall` | Bearer when configured | `handleBeforeRecall` | Prefetch memory context for a turn |
| `POST` | `/capture` | Bearer when configured | `handleTurnCommitted` | Record a user/assistant turn (L0 + pipeline notify) |
| `POST` | `/search/memories` | Bearer when configured | `searchMemories` | L1 structured memory search |
| `POST` | `/search/conversations` | Bearer when configured | `searchConversations` | L0 conversation search |
| `POST` | `/session/end` | Bearer when configured | `handleSessionEnd` | Flush session pipeline buffers |
| `POST` | `/seed` | Bearer when configured | `executeSeed` | Batch import historical conversations |
| `OPTIONS` | `*` | Not gated | — | CORS preflight; always `204` |

Unknown method/path combinations return **404** with the error envelope. Uncaught handler errors return **500**.

## Base URL, defaults, and clients

| Setting | Default | Env / config |
|---------|---------|----------------|
| Host | `127.0.0.1` | `TDAI_GATEWAY_HOST` / `server.host` |
| Port | `8420` | `TDAI_GATEWAY_PORT` / `server.port` |
| Data dir | `~/.memory-tencentdb/memory-tdai` (legacy `~/memory-tdai` if still present) | `TDAI_DATA_DIR` / `data.baseDir` / `MEMORY_TENCENTDB_ROOT` |
| API key | unset (auth off) | `TDAI_GATEWAY_API_KEY` / `server.apiKey` |
| CORS | no headers | `TDAI_CORS_ORIGINS` / `server.corsOrigins` |

Hermes uses `MemoryTencentdbSdkClient` (`hermes-plugin/memory/memory_tencentdb/client.py`) with default `base_url=http://127.0.0.1:8420` and optional Bearer from `MEMORY_TENCENTDB_GATEWAY_API_KEY` (fallback `TDAI_GATEWAY_API_KEY`). Client and Gateway secrets are configured independently.

```text
Hermes / HTTP client
        │  JSON + optional Authorization: Bearer
        ▼
  TdaiGateway (Node http)
        │  StandaloneHostAdapter + TdaiCore
        ▼
  L0/L1/L2/L3 store + pipeline
```

## Authentication

Auth is **opt-in**. When `server.apiKey` / `TDAI_GATEWAY_API_KEY` is unset, every route is open (legacy default). Startup logs a WARN if auth is disabled, and a louder WARN if the bind host is non-loopback without a key.

When a key is set:

- Every route **except** `GET /health` and `OPTIONS` requires `Authorization: Bearer <apiKey>`.
- Comparison uses constant-time equality (`crypto.timingSafeEqual`) after length check.
- Missing/malformed header → **401** `{ "error": "Unauthorized: missing Bearer token" }`.
- Wrong token → **401** `{ "error": "Unauthorized: invalid token" }`.

```http
Authorization: Bearer your-shared-secret
Content-Type: application/json
```

## CORS

| `corsOrigins` | Behavior |
|---------------|----------|
| `[]` (default) | No `Access-Control-*` headers; browsers block cross-origin |
| `["*"]` | Permissive `Access-Control-Allow-Origin: *` (dev only; startup WARN) |
| Explicit list | Echo request `Origin` only if listed; set `Vary: Origin` |

Allowed methods when CORS headers are emitted: `GET, POST, OPTIONS`. Allowed headers: `Content-Type, Authorization`.

## Error envelope

Standard error body:

```json
{
  "error": "human-readable message",
  "code": "optional-string"
}
```

Handlers typically set only `error`. Status codes:

| Status | When |
|--------|------|
| 400 | Missing required fields; invalid JSON body; seed validation failure |
| 401 | Auth enabled and Bearer missing/invalid |
| 404 | Unknown route |
| 500 | Unhandled exception (message from `Error.message`) |

`POST /seed` validation failures may include an extra field (not in the common type):

```json
{
  "error": "Seed validation failed",
  "validation_errors": [ /* stage/path details from SeedValidationError */ ]
}
```

Invalid JSON → **500** path via parse rejection message `"Invalid JSON body"` (thrown from the body parser and caught by the request router).

---

## GET /health

Always reachable without auth. Used by orchestrators (Docker/k8s health, `memory-tencentdb-ctl health`, Hermes startup).

### Response

| Field | Type | Meaning |
|-------|------|---------|
| `status` | `"ok" \| "degraded"` | `"ok"` if vector store is present; else `"degraded"` |
| `version` | string | Gateway version string (currently `"0.1.0"` in server) |
| `uptime` | number | Seconds since listen |
| `stores.vectorStore` | boolean | Vector/memory store initialized |
| `stores.embeddingService` | boolean | Embedding service initialized |

<RequestExample>
```bash
curl -sS http://127.0.0.1:8420/health
```
</RequestExample>

<ResponseExample>
```json
{
  "status": "ok",
  "version": "0.1.0",
  "uptime": 120,
  "stores": {
    "vectorStore": true,
    "embeddingService": true
  }
}
```
</ResponseExample>

---

## POST /recall

Prefetch memory context for the upcoming agent turn. Maps to `TdaiCore.handleBeforeRecall(query, session_key)`.

### Request body

<ParamField body="query" type="string" required>
User / turn text used for retrieval.
</ParamField>

<ParamField body="session_key" type="string" required>
Session identifier for scoped recall.
</ParamField>

<ParamField body="user_id" type="string">
Accepted by the request type and Hermes client; the current Gateway handler does not pass it into `TdaiCore`.
</ParamField>

### Response body

<ResponseField name="context" type="string">
System context string to append (`appendSystemContext`); empty string if none.
</ResponseField>

<ResponseField name="strategy" type="string">
Optional recall strategy label from core.
</ResponseField>

<ResponseField name="memory_count" type="number">
Count of recalled L1 memories (`recalledL1Memories.length`).
</ResponseField>

### Errors

- **400** — missing `query` or `session_key`

<RequestExample>
```bash
curl -sS -X POST http://127.0.0.1:8420/recall \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_KEY' \
  -d '{"query":"What did we decide about retention?","session_key":"agent:main:main"}'
```
</RequestExample>

<ResponseExample>
```json
{
  "context": "## Recalled memories\n...",
  "strategy": "hybrid",
  "memory_count": 3
}
```
</ResponseExample>

---

## POST /capture

Record one completed turn (sync path). Maps to `TdaiCore.handleTurnCommitted`. Starts the pipeline scheduler on first capture if needed.

### Request body

<ParamField body="user_content" type="string" required>
User message text.
</ParamField>

<ParamField body="assistant_content" type="string" required>
Assistant message text.
</ParamField>

<ParamField body="session_key" type="string" required>
Session key for L0 storage and pipeline state.
</ParamField>

<ParamField body="session_id" type="string">
Optional session id forwarded to capture.
</ParamField>

<ParamField body="user_id" type="string">
Accepted by the type/Hermes client; not consumed by the current handler.
</ParamField>

<ParamField body="messages" type="array">
Optional full message list. Default when omitted: `[{role:"user",content:user_content},{role:"assistant",content:assistant_content}]`.
</ParamField>

### Response body

<ResponseField name="l0_recorded" type="number">
Number of L0 rows recorded for this turn.
</ResponseField>

<ResponseField name="scheduler_notified" type="boolean">
Whether the extraction pipeline was notified.
</ResponseField>

### Errors

- **400** — missing `user_content`, `assistant_content`, or `session_key`

---

## POST /search/memories

L1 structured memory search. Maps to `TdaiCore.searchMemories`. Default `limit` inside core is **5** when omitted. Strategy is typically `hybrid`, `embedding`, `fts`, or `none` depending on store/embedding availability.

### Request body

<ParamField body="query" type="string" required>
Search query.
</ParamField>

<ParamField body="limit" type="number">
Max results (core default 5).
</ParamField>

<ParamField body="type" type="string">
Optional L1 type filter.
</ParamField>

<ParamField body="scene" type="string">
Optional scene filter.
</ParamField>

### Response body

| Field | Type | Meaning |
|-------|------|---------|
| `results` | string | Formatted search text (`formatSearchResponse`) |
| `total` | number | Hit count |
| `strategy` | string | Effective retrieval strategy |

### Errors

- **400** — missing `query`

---

## POST /search/conversations

L0 raw conversation search. Maps to `TdaiCore.searchConversations`. Default `limit` is **5** when omitted.

### Request body

<ParamField body="query" type="string" required>
Search query.
</ParamField>

<ParamField body="limit" type="number">
Max results (core default 5).
</ParamField>

<ParamField body="session_key" type="string">
Optional session scope.
</ParamField>

### Response body

| Field | Type | Meaning |
|-------|------|---------|
| `results` | string | Formatted conversation hits |
| `total` | number | Hit count |

### Errors

- **400** — missing `query`

---

## POST /session/end

Flush buffered pipeline work for a session. Maps to `TdaiCore.handleSessionEnd` → `scheduler.flushSession`. Unknown session keys are tolerated (no-op).

### Request body

<ParamField body="session_key" type="string" required>
Session to flush.
</ParamField>

<ParamField body="user_id" type="string">
Accepted by type/Hermes client; not consumed by the current handler.
</ParamField>

### Response body

```json
{ "flushed": true }
```

`flushed` is always `true` on the success path after `handleSessionEnd` returns (even if the session was unknown or the scheduler was absent).

### Errors

- **400** — missing `session_key`

---

## POST /seed

Batch-import historical conversations through the same validation and pipeline as the CLI seed command. **Blocking** — large payloads can take minutes. Hermes client default timeout for this call is **300s**.

### Request body

<ParamField body="data" type="unknown" required>
Seed payload: Format A or Format B (see below).
</ParamField>

<ParamField body="session_key" type="string">
Fallback session key when an input session omits one.
</ParamField>

<ParamField body="strict_round_role" type="boolean">
When true, each round must include both user and assistant roles.
</ParamField>

<ParamField body="auto_fill_timestamps" type="boolean">
Auto-fill missing timestamps. Default **true**.
</ParamField>

<ParamField body="config_override" type="object">
Deep-merged plugin config overrides on top of gateway `memory` config (plus injected `llm` from gateway LLM settings).
</ParamField>

### Seed input formats (`data`)

**Format A** — object wrapper:

```json
{
  "sessions": [
    {
      "sessionKey": "import:history-1",
      "sessionId": "optional",
      "conversations": [
        [
          { "role": "user", "content": "Hello", "timestamp": 1710000000000 },
          { "role": "assistant", "content": "Hi" }
        ]
      ]
    }
  ]
}
```

**Format B** — top-level array of the same session objects:

```json
[
  {
    "sessionKey": "import:history-1",
    "conversations": [
      [
        { "role": "user", "content": "Hello" },
        { "role": "assistant", "content": "Hi" }
      ]
    ]
  }
]
```

Message `timestamp` may be epoch ms or ISO-8601 string. Output is written under `{data.baseDir}/seed-YYYYMMDD-HHMMSS`.

### Response body

| Field | Type | Meaning |
|-------|------|---------|
| `sessions_processed` | number | Sessions in the run |
| `rounds_processed` | number | Conversation rounds processed |
| `messages_processed` | number | Messages processed |
| `l0_recorded` | number | L0 rows written |
| `duration_ms` | number | Wall time |
| `output_dir` | string | Seed output directory |

### Errors

- **400** — missing `data`
- **400** — validation failure with `error` + `validation_errors`

<RequestExample>
```bash
curl -sS -X POST http://127.0.0.1:8420/seed \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_KEY' \
  -d '{
    "data": {
      "sessions": [{
        "sessionKey": "seed:demo",
        "conversations": [[
          {"role":"user","content":"Project uses SQLite by default"},
          {"role":"assistant","content":"Noted."}
        ]]
      }]
    },
    "auto_fill_timestamps": true
  }'
```
</RequestExample>

---

## Hermes client mapping

| Client method | HTTP | Default timeout |
|---------------|------|-----------------|
| `health()` | `GET /health` | 3s |
| `recall(...)` | `POST /recall` | 10s |
| `capture(...)` | `POST /capture` | 10s |
| `search_memories(...)` | `POST /search/memories` | 10s |
| `search_conversations(...)` | `POST /search/conversations` | 10s |
| `end_session(...)` | `POST /session/end` | 10s |
| `seed(...)` | `POST /seed` | 300s |

When `api_key` is set on the client, every request (including health) sends `Authorization: Bearer ...`. The Gateway still does not require auth for `/health` even if a key is configured server-side.

## Operational notes

- **Stack**: Node `http` only; no framework middleware chain.
- **Concurrency**: Multiple `/capture` calls can hit the gateway while the pipeline scheduler starts once under a shared promise gate inside `TdaiCore`.
- **Security posture**: Prefer loopback bind or set `TDAI_GATEWAY_API_KEY` before exposing the port. Align Hermes `MEMORY_TENCENTDB_GATEWAY_API_KEY` with the Gateway key.
- **Lifecycle**: Start/stop/status/logs via `memory-tencentdb-ctl` (see Gateway lifecycle docs). Config file resolution: `TDAI_GATEWAY_CONFIG` → `./tdai-gateway.yaml|json` → `<dataDir>/tdai-gateway.yaml|json` → env-only.
- **LLM on seed**: Seed injects gateway `llm` (`baseUrl`, `apiKey`, `model`, …) into the seed plugin config so extraction can run without OpenClaw.

## Quick verification

```bash
# Probe (no auth)
curl -sS http://127.0.0.1:8420/health

# Auth check (expect 401 if key configured and header wrong)
curl -sS -o /dev/null -w '%{http_code}\n' -X POST http://127.0.0.1:8420/recall \
  -H 'Content-Type: application/json' \
  -d '{"query":"x","session_key":"s"}'
```

## Related pages

<CardGroup>
  <Card title="Secure the Gateway" href="/secure-gateway">
    Bearer auth, CORS allow-list, non-loopback warnings, Hermes key alignment.
  </Card>
  <Card title="Hermes setup" href="/hermes-setup">
    Install memory_tencentdb, Gateway discovery, and health checks.
  </Card>
  <Card title="Seed historical conversations" href="/seed-history">
    Format A/B details, CLI flags, config overrides, L0→L1→L2→L3 path.
  </Card>
  <Card title="Gateway lifecycle" href="/gateway-ops">
    memory-tencentdb-ctl start/stop/status/health/logs and path layout.
  </Card>
  <Card title="Environment variables" href="/environment-variables">
    TDAI_* and MEMORY_TENCENTDB_* keys, config resolution order.
  </Card>
  <Card title="TdaiCore and host adapters" href="/tdai-core-adapters">
    How HTTP handlers map to TdaiCore vs in-process OpenClaw hooks.
  </Card>
  <Card title="Agent tools" href="/agent-tools">
    OpenClaw and Hermes tool schemas for memory/conversation search.
  </Card>
  <Card title="Troubleshooting" href="/troubleshooting">
    Auth 401, circuit breaker, embedding failures, probe checklist.
  </Card>
</CardGroup>
