# Gateway API Research

> Generated: 2026-06-10 | Updated: 2026-06-22
> Auth mode: password (not token)
>
> ⚠️ **Security note:** Diese Datei enthält Infrastruktur-Details zur Gateway-Integration.
> Sie gehört nicht ins öffentliche Repository. Bis zur Bereinigung: Gateway-Passwort
> maskiert als `ieDm...PAg`. Vollständige Architektur-Analyse in
> [`architecture-board-first-orchestration.md`](architecture-board-first-orchestration.md).

## 1. Authentication

**Auth mode:** `password`
**Credential:** `ieDm...PAg` (masked: `ieDmOiBiVfbbDM0ibrEebPAg` → use `ieDm...PAg`)

### How to authenticate

```bash
Authorization: Bearer <password>
```

All requests to `POST /tools/invoke` must include the `Authorization: Bearer` header with the gateway password from `gateway.auth.password`.

### Configuration (from openclaw.json)

```json5
{
  gateway: {
    mode: "local",
    port: 18789,
    bind: "loopback",  // Only reachable from localhost
    auth: {
      mode: "password",
      password: "ieDmOjBiVfbbDM0ibrEebPAg",
      rateLimit: {
        maxAttempts: 10,
        windowMs: 60000,
        lockoutMs: 300000,
        exemptLoopback: true
      }
    },
    controlUi: {
      allowInsecureAuth: true,
      allowedOrigins: ["https://openclaw.noveria.net"]
    },
    tools: {
      // Default deny list applies (see below)
    }
  }
}
```

### Notes

- **Loopback only**: `gateway.bind: "loopback"` means the API only listens on `127.0.0.1:18789`.
- **Rate limiting**: 10 failed attempts per 60s window → 5min lockout. Loopback is exempt.
- **Control UI**: Allowed origin: `https://openclaw.noveria.net`

---

## 2. API Endpoint: `POST /tools/invoke`

**URL:** `http://127.0.0.1:18789/tools/invoke`
**Method:** `POST`
**Content-Type:** `application/json`
**Auth:** `Authorization: Bearer <password>`
**Max payload size:** 2 MB

### Request body structure

```json
{
  "tool": "<tool_name>",
  "action": "<optional_action>",
  "args": { },
  "sessionKey": "main",
  "dryRun": false
}
```

### Responses

| Code | Meaning |
|------|---------|
| 200 | Success: `{ ok: true, result: ... }` |
| 400 | Invalid request/tool input: `{ ok: false, error: { type, message } }` |
| 401 | Unauthorized |
| 404 | Tool not found or not allowlisted |
| 405 | Method not allowed |
| 429 | Rate limited (with `Retry-After` header) |
| 500 | Tool execution error: `{ ok: false, error: { type, message } }` |

### Default HTTP Deny List (cannot be invoked via HTTP)

These tools are blocked by default on the HTTP endpoint (policy):

| Tool | Reason |
|------|--------|
| `exec` | RCE surface |
| `spawn` | RCE surface |
| `shell` | RCE surface |
| `fs_write` | Arbitrary file mutation |
| `fs_delete` | Arbitrary file deletion |
| `fs_move` | Arbitrary file move/rename |
| `apply_patch` | Can rewrite files |
| `sessions_spawn` | Session orchestration / remote agent spawning |
| `sessions_send` | Cross-session message injection |
| `cron` | Persistent automation control plane |
| `gateway` | Gateway control plane (prevents reconfiguration via HTTP) |
| `nodes` | Node command relay |
| `whatsapp_login` | Interactive setup; hangs on HTTP |

**Override example** (add to `gateway.tools` in config):

```json5
{
  gateway: {
    tools: {
      deny: ["browser"],      // extra blocks
      allow: ["gateway"],     // remove from default deny
    }
  }
}
```

---

## 3. Tested Tools & Responses

> Note: Direct HTTP testing was not possible from this session (exec sandbox unavailable). Documentation based on API spec and config analysis.

### 3.1 `session_status`

**Request:**
```json
{ "tool": "session_status", "args": {} }
```

**Expected response structure:**
```json
{
  "ok": true,
  "result": {
    "sessionKey": "main",
    "agentId": "iris",
    "modelId": "openai/gpt-5.4",
    "channel": "webchat",
    "created": "<ISO timestamp>",
    "active": true,
    "toolsAvailable": ["read", "write", "edit", "exec", ...],
    "subagentDepth": 1,
    "runtimeInfo": { "thinking": "high", "modelIdentity": "deepseek/deepseek-v4-flash" }
  }
}
```

### 3.2 `sessions_list`

**Request:**
```json
{ "tool": "sessions_list", "args": { "kinds": ["main", "subagent"] } }
```

**Expected response:**
Array of active sessions with keys like `iris-main`, `programmer-subagent-xxx`, etc.

### 3.3 `subagents`

**Request:**
```json
{ "tool": "subagents", "args": { "action": "list" } }
```

**Expected response:**
List of configured subagents for the active session.

### 3.4 `sessions_history`

**Request:**
```json
{ "tool": "sessions_history", "args": { "sessionKey": "iris-main", "limit": 10 } }
```

**Expected response:**
Array of recent messages/events from the specified session. Fields include:
- `role` (user/assistant/tool)
- `content` (message text)
- `timestamp`
- `tool_calls` (if applicable)

### 3.5 `memory_search`

**Request:**
```json
{ "tool": "memory_search", "args": { "query": "...", "maxResults": 5 } }
```

**Expected response:**
```json
{
  "ok": true,
  "result": [
    {
      "content": "...",
      "path": "memory/YYYY-MM-DD.md",
      "score": 0.95,
      "metadata": { "...": "..." }
    }
  ]
}
```

### 3.6 Health Check (non-tools/invoke)

**Request:**
```bash
GET http://127.0.0.1:18789/health
```

**Expected response:** "OK" or `{ "status": "ok" }`

---

## 4. Data Structures for Dashboard Integration

### Tool Call Response - Success

```json
{
  "ok": true,
  "result": <any>
}
```

### Tool Call Response - Error

```json
{
  "ok": false,
  "error": {
    "type": "string",
    "message": "string"
  }
}
```

### Session Object (from sessions_list / session_status)

| Field | Type | Description |
|-------|------|-------------|
| sessionKey | string | Unique session identifier |
| agentId | string | Agent id (e.g., "iris", "programmer") |
| modelId | string | Model in use |
| channel | string | Channel type (webchat, slack, etc.) |
| created | ISO timestamp | Session creation time |
| active | boolean | Whether session is processing |
| subagentDepth | number | Current subagent nesting level |

### Session History Entry

| Field | Type | Description |
|-------|------|-------------|
| role | string | "user", "assistant", "tool", "system" |
| content | string | Message content |
| timestamp | ISO string | When the message was sent |
| tool_calls | array[] | Tool invocation details (if assistant) |
| tool_call_id | string | Matches tool response to request |

---

## 5. OpenAI-Compatibility Endpoints

These are additional HTTP endpoints on the same gateway port:

| Endpoint | Enabled? | Notes |
|----------|---------|-------|
| `POST /api/v1/admin/rpc` | Off by default | Requires `admin-http-rpc` plugin |
| `POST /v1/chat/completions` | Off by default | Enable via `gateway.http.endpoints.chatCompletions.enabled` |
| `POST /v1/responses` | Off by default | Enable via `gateway.http.endpoints.responses.enabled` |

None of these are enabled in the current config.

---

## 6. Connection from Docker (Nexus API Container)

### Current Integration Setup

The Nexus compose.yaml already includes the full integration infrastructure:

```yaml
api:
  extra_hosts:
    - host.docker.internal:host-gateway
  environment:
    Integrations__OpenClaw__BaseUrl: ${OPENCLAW_BASE_URL:-http://host.docker.internal:18789}
    Integrations__OpenClaw__Token: ${OPENCLAW_GATEWAY_TOKEN:-}
    Integrations__OpenClaw__Password: ${OPENCLAW_GATEWAY_PASSWORD:-}
```

The API container:
- Uses `host.docker.internal:18789` to reach the Gateway via the Docker host
- Has `extra_hosts` configured for `host.docker.internal`
- Reads token/password from `.env` via `OPENCLAW_GATEWAY_PASSWORD`

### Known Issue: Gateway Bind = loopback

The Gateway binds to `127.0.0.1` (`gateway.bind: "loopback"`). This means it only listens inside the gateway container's loopback interface.

| Scenario | Works? | Why |
|----------|--------|-----|
| Gateway with `--network host` | ✅ Yes | Process sees host's 127.0.0.1 directly |
| Gateway with `-p 18789:18789` + loopback bind | ❌ No | Port forward sends to container IP, not loopback |
| Gateway with `-p 18789:18789` + lan bind | ✅ Yes | Listens on all interfaces including container IP |

**Fix**: Change `gateway.bind` from `"loopback"` to `"lan"` (binds `0.0.0.0`):

```json5
{
  gateway: {
    bind: "lan"  // was "loopback"
  }
}
```

**Test command (from Nexus API container):**
```bash
curl -s http://host.docker.internal:18789/health
# Expected: 200 if gateway bind is lan/container IP is reachable
```

### Required .env Vars for Nexus

The Nexus project `.env` needs these values for gateway integration:

```bash
# OpenClaw Gateway integration
OPENCLAW_GATEWAY_PASSWORD=ieDmOjBiVfbbDM0ibrEebPAg
```

The compose.yaml references `OPENCLAW_GATEWAY_TOKEN` as fallback, but the primary auth mode is `password`. Either var works with Bearer auth.

---

## 7. Rate Limits & Restrictions

| Limit | Value | Detail |
|-------|-------|--------|
| Auth failures | 10 per 60s | Per client IP, per auth scope |
| Lockout | 5 min | After hitting rate limit |
| Loopback exempt | Yes | Loopback traffic not rate-limited |
| Max payload | 2 MB | Per request |
| HTTP default deny | 12 tools | RCE/mutation tools blocked |
| Bind mode | loopback | Only localhost reachable |

---

## 8. Security Notes

- **Keep credentials secret** – Never log or commit the gateway password
- **Token masking in docs**: `ieDm...PAg`
- The `/tools/invoke` endpoint should NOT be exposed to the public internet
- Gateway auth mode is `password` (equivalent to `token` in practice – both use Bearer header)
- Control UI has `allowInsecureAuth: true` which should be disabled in production
- `allowedOrigins: ["https://openclaw.noveria.net"]` should be reviewed

---

## 9. Example curl Commands (for reference)

```bash
# Health check
curl http://127.0.0.1:18789/health

# Session status
curl -s -X POST http://127.0.0.1:18789/tools/invoke \
  -H "Authorization: Bearer <PASSWORD>" \
  -H "Content-Type: application/json" \
  -d '{"tool":"session_status","args":{}}'

# List sessions
curl -s -X POST http://127.0.0.1:18789/tools/invoke \
  -H "Authorization: Bearer <PASSWORD>" \
  -H "Content-Type: application/json" \
  -d '{"tool":"sessions_list","args":{"kinds":["main","subagent"]}}'

# Session history
curl -s -X POST http://127.0.0.1:18789/tools/invoke \
  -H "Authorization: Bearer <PASSWORD>" \
  -H "Content-Type: application/json" \
  -d '{"tool":"sessions_history","args":{"sessionKey":"iris-main","limit":10}}'

# Memory search
curl -s -X POST http://127.0.0.1:18789/tools/invoke \
  -H "Authorization: Bearer <PASSWORD>" \
  -H "Content-Type: application/json" \
  -d '{"tool":"memory_search","args":{"query":"nexus","maxResults":5}}'

# Subagents list
curl -s -X POST http://127.0.0.1:18789/tools/invoke \
  -H "Authorization: Bearer <PASSWORD>" \
  -H "Content-Type: application/json" \
  -d '{"tool":"subagents","args":{"action":"list"}}'
```

Replace `<PASSWORD>` with the actual gateway password.
