@flame0510/project-aether 1.1.14 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  # Rev4a API Reference
2
2
 
3
- > **Last updated:** 2026-07-19
3
+ > **Last updated:** 2026-09-05
4
4
 
5
5
  All routes are under `/api/`. Authentication is required on every endpoint
6
6
  unless otherwise noted.
@@ -68,50 +68,81 @@ Delete the `rev4a_token` cookie.
68
68
 
69
69
  ---
70
70
 
71
- ## Onboarding
71
+ ### `GET /api/auth/status`
72
+ Whether a password has been configured, in the environment or in the `.env` the
73
+ setup wizard writes. Used by the frontend to choose between the setup wizard and
74
+ the login form.
72
75
 
73
- ### `GET /api/onboarding/status`
74
- Returns per-step onboarding progress.
76
+ **Auth:** none — it reveals only whether setup has happened, never the password
75
77
 
76
- **Auth:** browser cookie
78
+ **Response:** `{ "configured": true }`
77
79
 
78
- **Response:**
79
- ```json
80
- {
81
- "complete": false,
82
- "steps": { "welcome": true, "providers": false, "agents": false },
83
- "completedAt": null
84
- }
85
- ```
80
+ ---
86
81
 
87
- If no progress file exists, returns all steps as `false`.
82
+ ## Setup & wizard
88
83
 
89
- ### `POST /api/onboarding/complete`
90
- Mark a step as completed, complete all steps, or reset the wizard.
84
+ ### `GET /api/wizard/status`
85
+ Whether the first-run wizard has been completed.
91
86
 
92
- **Auth:** browser cookie
87
+ **Auth:** browser cookie or bearer token
93
88
 
94
- **Body:**
95
- | Shape | Effect |
96
- |---|---|
97
- | `{ "step": "providers" }` | Mark a single step as done |
98
- | `{}` | Mark all steps as done (complete onboarding) |
99
- | `{ "reset": true }` | Reset all steps to `false` (restart wizard) |
89
+ **Response:** `{ "done": true }`
100
90
 
101
- **Response:**
102
- ```json
103
- {
104
- "status": "ok",
105
- "steps": { "welcome": true, "providers": true, "agents": false },
106
- "complete": false
107
- }
108
- ```
91
+ ### `POST /api/wizard/complete`
92
+ Mark the wizard as completed.
93
+
94
+ **Auth:** browser cookie or bearer token
95
+
96
+ **Response:** `{ "done": true }`
97
+
98
+ ### `POST /api/wizard/reset`
99
+ Reset the wizard so it runs again.
100
+
101
+ **Auth:** browser cookie or bearer token
102
+
103
+ **Response:** `{ "done": false }`
104
+
105
+ ---
106
+
107
+ ### `POST /api/setup/password`
108
+ Set the dashboard password during first-run setup.
109
+
110
+ **Auth:** none, and only usable once — returns `409` if a password already exists
111
+
112
+ **Body:** `{ "password": "..." }` — minimum 8 characters
113
+
114
+ **Errors:** `400` invalid JSON, missing password, or shorter than 8 characters;
115
+ `409` already configured; `500` the `.env` file could not be written.
116
+
117
+ **Response:** `{ "status": "ok" }`
118
+
119
+ ### `POST /api/setup/restart`
120
+ Restart the service so a freshly written `.env` takes effect.
121
+
122
+ **Auth:** open before a password exists, then browser cookie or bearer token (`requireAuthIfConfigured`)
123
+
124
+ **Errors:** `500` if the `.env` file is missing, or the restart could not be issued.
125
+
126
+ ### `GET /api/setup/agent-image`
127
+ Whether the `openclaw-agent-base` image is present.
128
+
129
+ **Auth:** open before a password exists, then browser cookie or bearer token (`requireAuthIfConfigured`)
130
+
131
+ **Response:** `{ "exists": true, "imageId": "sha256:..." }`, or `{ "exists": false }`
132
+ when the image is absent **or Docker is unreachable** — the two are not distinguished.
133
+
134
+ ### `POST /api/setup/agent-image`
135
+ Pull or build the agent base image, streaming progress.
136
+
137
+ **Auth:** open before a password exists, then browser cookie or bearer token (`requireAuthIfConfigured`)
138
+
139
+ **Errors:** `409` if a download is already running.
109
140
 
110
141
  ---
111
142
 
112
143
  ## Gateway
113
144
 
114
- The Gateway API reads the model catalogue from [`models.config.json`](../models.config.json)
145
+ The Gateway API reads the model catalogue from [`models.config.json`](../../models.config.json)
115
146
  (tracked in the repository). Full documentation at [GATEWAY.md](GATEWAY.md) and
116
147
  [PROVIDERS.md](PROVIDERS.md).
117
148
 
@@ -123,34 +154,57 @@ Returns live gateway status from all agent containers.
123
154
  **Response:**
124
155
  ```json
125
156
  {
157
+ "timestamp": "2026-08-27T22:00:00.000Z",
158
+ "gateway": "Rev4a Model Gateway",
159
+ "status": "online",
160
+ "models": { "total": 0, "available": 0, "byProvider": [], "list": [] },
126
161
  "agents": {
127
162
  "total": 1,
128
163
  "list": [
129
164
  {
130
- "containerName": "openclaw-atlas",
131
165
  "agentId": "atlas",
132
- "agentName": "Atlas",
166
+ "containerName": "openclaw-atlas",
167
+ "state": "running",
133
168
  "defaultModel": "rev4a/deepseek/deepseek-v4-flash",
134
- "configured": true
169
+ "fallbacks": [],
170
+ "aliases": {},
171
+ "providers": []
135
172
  }
136
173
  ]
137
- }
174
+ },
175
+ "aliases": {},
176
+ "coreModel": { "defaultModel": "rev4a/deepseek/deepseek-v4-flash", "fallbacks": [] },
177
+ "apiKeys": { "configured": [], "all": [] }
138
178
  }
139
179
  ```
140
180
 
181
+ **`models`, the top-level `aliases`, and `apiKeys` are always empty here.** They
182
+ exist only to keep the response shape stable for existing consumers. The model
183
+ catalogue is served by `GET /api/gateway/provider`, which reads the bundled
184
+ config plus user overrides from disk; provider credential state comes from the
185
+ same route (`configured` per provider, out of `provider-keys.json`).
186
+
187
+ Per-agent values are read from each container's `openclaw.json`, one
188
+ `docker exec … cat` per agent, run concurrently. An agent whose config cannot be
189
+ read is still listed, with an `error` field and empty values, rather than
190
+ failing the whole response.
191
+
141
192
  ### `PUT /api/gateway/provider`
142
- Trigger a full provider model sync to all agent containers.
193
+ Enable or disable a single model in the catalogue, then sync every agent.
143
194
 
144
- **Auth:** any auth method
195
+ **Auth:** browser cookie or bearer token
145
196
 
146
- **Body:** none (reads current provider state from `models.config.json` and `data/provider-keys.json`)
197
+ **Body:** `{ "modelId": "deepseek/deepseek-chat", "enabled": true }` — both
198
+ required. Missing either returns `400`; an unknown `modelId` returns `404`.
147
199
 
148
200
  **Response:**
149
201
  ```json
150
202
  {
151
203
  "status": "ok",
152
- "activeModels": 2,
153
- "results": ["Active models: 2 across 1 agent(s)"]
204
+ "modelId": "deepseek/deepseek-chat",
205
+ "enabled": true,
206
+ "provider": "deepseek",
207
+ "sync": ["Active models: 2 across 1 agent(s)"]
154
208
  }
155
209
  ```
156
210
 
@@ -264,6 +318,55 @@ Rev4a Provider Gateway — proxy chat completions to the correct upstream.
264
318
 
265
319
  ---
266
320
 
321
+ ### `GET /api/models`
322
+ Models eligible to be used: the catalogue filtered down to those whose provider
323
+ has a key configured.
324
+
325
+ **Auth:** browser cookie or bearer token
326
+
327
+ ### `GET /api/gateway/provider/balance`
328
+ Remaining credit for one provider.
329
+
330
+ **Auth:** browser cookie or bearer token
331
+
332
+ **Query params:** `provider` — one of `deepseek`, `openrouter`, `glm`.
333
+
334
+ **Errors:** `502` when the provider's API cannot be reached or rejects the call.
335
+
336
+ ### `POST /api/gateway/provider/oauth`
337
+ Run the OAuth device flow for a provider that supports it.
338
+
339
+ **Auth:** browser cookie or bearer token
340
+
341
+ **Body:** `{ "provider": "..." }`
342
+
343
+ **Errors:** `400` for an unknown provider; the response body carries `available`
344
+ with the providers that do support OAuth.
345
+
346
+ ### `POST /api/aliases/add`
347
+ Add a model alias.
348
+
349
+ **Auth:** browser cookie or bearer token
350
+
351
+ **Body:** `{ "alias": "fast", "model": "deepseek/deepseek-chat" }`
352
+
353
+ **Response:** `{ "ok": true, "output": "..." }` — `output` is the CLI's stdout.
354
+
355
+ **Errors:** `400` if `alias` or `model` is missing.
356
+
357
+ ### `POST /api/aliases/remove`
358
+ Remove a model alias.
359
+
360
+ **Auth:** browser cookie or bearer token
361
+
362
+ **Body:** `{ "alias": "fast" }`
363
+
364
+ **Response:** `{ "ok": true, "output": "..." }`
365
+
366
+ **Errors:** `400` if `alias` is missing.
367
+
368
+ ---
369
+
267
370
  ## Config
268
371
 
269
372
  ### `PUT /api/config/env`
@@ -344,6 +447,17 @@ Returns a single session with its events and child sessions.
344
447
 
345
448
  ---
346
449
 
450
+ ### `GET /api/sessions/[id]`
451
+ One session with its events and any child sessions.
452
+
453
+ **Auth:** browser cookie or bearer token
454
+
455
+ **Response:** `{ "session": {…}, "events": [...], "children": [...] }`
456
+
457
+ **Errors:** `404` if the session does not exist.
458
+
459
+ ---
460
+
347
461
  ## Stats & Costs
348
462
 
349
463
  ### `GET /api/stats`
@@ -612,7 +726,12 @@ Create a new agent container from a template.
612
726
  1. OpenClaw gateway starts with `--allow-unconfigured`, generating its own default config
613
727
  2. The route waits for the gateway to be fully up (health check poll, up to 60 s)
614
728
  3. Once ready, writes `gateway.controlUi.allowedOrigins` + `agents.defaults.model.primary` + fallbacks
615
- 4. Writes `models.providers.rev4a` + extraDirs + update config → single `openclaw.json` write → single `gateway restart`
729
+ 4. Writes `models.providers.rev4a` + extraDirs + update config → single `openclaw.json` write
730
+
731
+ No gateway restart is issued. OpenClaw's `gateway.reload` defaults to `hybrid`:
732
+ it hot-applies changes under `agents`, `models` and `routing`, and restarts only
733
+ for `gateway.*`, `discovery` and `plugins.load`. Restarting here repeated work
734
+ the Gateway already does.
616
735
 
617
736
  > **Note:** The entrypoint no longer generates `openclaw.json`. All post-creation config is written by the create route after gateway readiness, eliminating the race condition where the entrypoint would overwrite synced provider config.
618
737
 
@@ -762,6 +881,21 @@ aligning the volume's state/config to the new binary.
762
881
 
763
882
  ---
764
883
 
884
+ ### `POST /api/agents/[id]/lifecycle`
885
+ Start, stop or restart an agent container.
886
+
887
+ **Auth:** browser cookie or bearer token
888
+
889
+ **Body:** `{ "action": "start" | "stop" | "restart" }`
890
+
891
+ Redundant actions are no-ops: starting a running container, or stopping a stopped
892
+ one, succeeds without touching it.
893
+
894
+ **Errors:** `400` for a malformed body, an unknown action, or an invalid agent id;
895
+ `404` when no container carries that `AGENT_ID`.
896
+
897
+ ---
898
+
765
899
  ## Channels
766
900
 
767
901
  Channel APIs read/write agent channel configuration (Telegram) directly inside agent containers via `docker exec`. All endpoints require the agent container to be running.
@@ -987,7 +1121,10 @@ Update the shared agents gateway token and push it to all running agent containe
987
1121
  ### `GET /api/agents/image-status`
988
1122
  Check the status of the `openclaw-agent-base` Docker image.
989
1123
 
990
- **Auth:** public (no auth required)
1124
+ **Auth:** browser cookie or bearer token
1125
+
1126
+ The ghcr.io digest lookup is cached for 10 minutes and de-duplicated while in
1127
+ flight, so polling this endpoint does not re-query the registry every time.
991
1128
 
992
1129
  **Response:**
993
1130
  ```json
@@ -1011,7 +1148,7 @@ Starts pulling or building the `openclaw-agent-base:latest` image. Returns 202
1011
1148
  Accepted immediately — the operation runs in the background and writes logs.
1012
1149
  The frontend polls `GET /api/agents/image-status` for progress updates.
1013
1150
 
1014
- **Auth:** public (no auth required)
1151
+ **Auth:** browser cookie or bearer token
1015
1152
 
1016
1153
  **Response (202):**
1017
1154
  ```json
@@ -1023,17 +1160,7 @@ The frontend polls `GET /api/agents/image-status` for progress updates.
1023
1160
  { "error": "A download is already in progress" }
1024
1161
  ```
1025
1162
 
1026
- ### `DELETE /api/agents/abort-download`
1027
- Kills an in-progress docker pull/build.
1028
-
1029
- **Auth:** public (no auth required)
1030
-
1031
- **Response:**
1032
- ```json
1033
- { "aborted": true }
1034
- ```
1035
-
1036
- Image state is managed in `lib/buildAgentImage.ts`:
1163
+ Image state is managed in `lib/buildAgentImage.ts` (library functions, not HTTP routes):
1037
1164
  - `downloadAgentImage(opts)` — attempts `docker pull` from registry first, then falls back to `docker build`
1038
1165
  all stdout/stderr to a log file, supports optional `onEvent` callback and `AbortSignal`
1039
1166
  - `abortDownload()` — sends SIGKILL to the child process, releases the download lock
@@ -1361,93 +1488,23 @@ Write content to a workspace file.
1361
1488
 
1362
1489
  ---
1363
1490
 
1364
- ## Vault
1491
+ ## Provider keys and credentials
1365
1492
 
1366
- Full key management documentation at [PROVIDERS.md](PROVIDERS.md).
1367
-
1368
- ### `GET /api/vault`
1369
- List all stored credentials (keys masked).
1370
-
1371
- **Auth:** browser cookie
1372
-
1373
- **Response:**
1374
- ```json
1375
- {
1376
- "providers": {
1377
- "deepseek": { "keyStatus": "present", "scoped": ["atlas"] }
1378
- },
1379
- "services": {
1380
- "github": { "tokenStatus": "present", "user": "Flame0510", "scoped": ["argus", "atlas"] }
1381
- }
1382
- }
1383
- ```
1493
+ Provider API keys are read with `GET /api/gateway/provider` and set with
1494
+ `POST /api/gateway/provider` — documented in `docs/dev/PROVIDERS.md`, which is the
1495
+ only file carrying that endpoint's request bodies. `PUT` toggles a model, not a
1496
+ key. The keys are stored in `data/provider-keys.json`.
1497
+ Third-party service credentials are managed under `/api/credentials`
1498
+ (documented below) and stored in `credentials.db`.
1384
1499
 
1385
- ### `GET /api/vault/provider/key`
1386
- Return the full API key for a provider.
1500
+ There is no `/api/vault` route, and no per-credential agent scoping: any stored
1501
+ credential can be synced to any container.
1387
1502
 
1388
- **Auth:** browser cookie
1389
-
1390
- **Query params:** `provider` (required), `agent` (optional)
1391
-
1392
- **Response (200):**
1393
- ```json
1394
- {
1395
- "provider": "deepseek",
1396
- "apiKey": "sk-...full-key...",
1397
- "masked": "sk-...be03",
1398
- "source": "local"
1399
- }
1400
- ```
1401
-
1402
- **Error:** `400` if `provider` missing; `404` if key not found.
1403
-
1404
- See [PROVIDERS.md](PROVIDERS.md#get-apivaultproviderkey) for full detail.
1405
-
1406
- ### `POST /api/vault/provider`
1407
- Add or update a provider API key.
1408
-
1409
- **Auth:** browser cookie
1410
-
1411
- **Body:** `{ "provider": "deepseek", "apiKey": "sk-...", "baseUrl": "https://api.deepseek.com" }`
1412
-
1413
- **Response:** `{ "status": "ok", "provider": "deepseek", "masked": "sk-...be03", "updatedAt": 1749200000000 }`
1414
-
1415
- ### `DELETE /api/vault/provider`
1416
- Remove a provider and all its keys.
1417
-
1418
- **Auth:** browser cookie
1419
-
1420
- **Body:** `{ "provider": "deepseek" }`
1421
-
1422
- **Response:** `{ "status": "removed" }`
1423
-
1424
- ### `PUT /api/vault/service`
1425
- Add or update a service token.
1426
-
1427
- **Auth:** browser cookie
1428
-
1429
- **Body:** `{ "id": "github", "token": "***", "user": "Flame0510", "scopes": ["atlas"] }`
1430
-
1431
- **Response:** `{ "ok": true }`
1432
-
1433
- ### `DELETE /api/vault/service`
1434
- Remove a service.
1435
-
1436
- **Auth:** browser cookie
1437
-
1438
- **Body:** `{ "id": "github" }`
1439
-
1440
- **Response:** `{ "ok": true }`
1441
-
1442
- ### `PUT /api/vault/permissions`
1443
- Update agent permissions for a credential.
1444
-
1445
- **Body:** `{ "type": "provider" | "service", "id": "deepseek", "scopes": ["atlas", "argus"] }`
1446
-
1447
- **Response:** `{ "ok": true }`
1503
+ Full key management documentation at [PROVIDERS.md](PROVIDERS.md).
1448
1504
 
1449
1505
  ---
1450
1506
 
1507
+
1451
1508
  ## Containers
1452
1509
 
1453
1510
  ### `GET /api/containers`
@@ -1472,24 +1529,13 @@ List all running Docker containers with resource usage.
1472
1529
  }
1473
1530
  ```
1474
1531
 
1475
- ### `GET /api/containers/logs?name=<name>&tail=<lines>`
1476
- Get container logs.
1477
-
1478
- **Auth:** browser cookie
1479
-
1480
- **Query params:** `name` (required), `tail` (default 50)
1481
-
1482
- **Response:** `{ "name": "openclaw-atlas", "logs": "[log lines...]" }`
1483
-
1484
- ### `POST /api/containers/action`
1485
- Execute action on a container.
1486
-
1487
- **Body:** `{ "action": "restart" | "stop" | "start", "name": "openclaw-atlas" }`
1488
-
1489
- **Response:** `{ "ok": true, "message": "Container openclaw-atlas restarted" }`
1532
+ The container list is the only container route. There is no logs endpoint and no
1533
+ action endpoint: logs are read through the terminal WebSocket, and lifecycle
1534
+ operations on agent containers go through `/api/agents/[id]/lifecycle`.
1490
1535
 
1491
1536
  ---
1492
1537
 
1538
+
1493
1539
  ## Tools
1494
1540
 
1495
1541
  ### `GET /api/tools-config`
@@ -1522,30 +1568,16 @@ Return the installed OpenClaw CLI version.
1522
1568
 
1523
1569
  **Fallback:** `{ "version": "unknown" }` if the command fails.
1524
1570
 
1525
- ### `GET /api/ws`
1526
- Documentation placeholder for WebSocket access.
1527
-
1528
- **Auth:** browser cookie
1571
+ ### Terminal WebSocket
1529
1572
 
1530
- **Response:** plain text with HTTP `426 Upgrade Required`
1531
-
1532
- Example body:
1533
- ```text
1534
- WebSocket endpoint available at ws://HOST/ws. This route is a documentation placeholder and does not upgrade connections.
1535
- ```
1536
-
1537
- ### `GET /ws`
1538
- Live WebSocket endpoint exposed by the custom server.
1539
-
1540
- **Auth:** same browser session as the dashboard
1541
-
1542
- **Protocol notes:**
1543
- - Hosted on the main HTTP port, not under `/api/`
1544
- - Accepts JSON messages such as `chat.send` and `chat.history`
1545
- - Intended for the dashboard client rather than generic REST consumers
1573
+ Not an `/api/` route and not on the dashboard port. `terminal-ws-server.js`
1574
+ listens on **`ws://127.0.0.1:3741`** (`TERMINAL_WS_PORT`), bound to the loopback
1575
+ interface only, and speaks the PTY protocol described in
1576
+ [CONTAINER-TERMINAL.md](../CONTAINER-TERMINAL.md).
1546
1577
 
1547
1578
  ---
1548
1579
 
1580
+
1549
1581
  ## Plugins
1550
1582
 
1551
1583
  ### `GET /api/plugins`
@@ -1745,11 +1777,19 @@ data: [DONE]
1745
1777
  ### `GET /api/credentials`
1746
1778
  List all stored credential profiles and the provider registry.
1747
1779
 
1780
+ **Auth:** browser cookie or bearer token
1781
+
1748
1782
  **Response:** `{ profiles: CredentialProfile[], providers: CredentialProviderDef[] }`
1749
1783
 
1784
+ Secrets are never returned here — each profile carries only `secretMasked`.
1785
+
1786
+ Each entry in a provider's `fields` may carry `docsUrl` and `docsLabel`, pointing at the page where that specific value is created. The provider-level `docsUrl` is the general one shown on the card; the field-level pair is what the add/edit form renders next to the input it is asking you to fill.
1787
+
1750
1788
  ### `POST /api/credentials`
1751
1789
  Create a new credential profile.
1752
1790
 
1791
+ **Auth:** browser cookie or bearer token
1792
+
1753
1793
  **Body:** `{ providerId: string, label: string, secret: Record<string, string> }`
1754
1794
 
1755
1795
  **Response:** `{ id: string }` (201)
@@ -1757,30 +1797,77 @@ Create a new credential profile.
1757
1797
  ### `GET /api/credentials/[id]`
1758
1798
  Get a single credential profile (no secret returned).
1759
1799
 
1800
+ **Auth:** browser cookie or bearer token
1801
+
1760
1802
  ### `PATCH /api/credentials/[id]`
1761
1803
  Update label and/or secret of a credential.
1762
1804
 
1805
+ **Auth:** browser cookie or bearer token
1806
+
1763
1807
  **Body:** `{ label: string, secret: Record<string, string> }`
1764
1808
 
1809
+ Secret fields are **merged** onto the stored payload, not substituted for it:
1810
+ only the fields carrying a non-empty value are written, and every blank field
1811
+ keeps whatever is already stored. This matters for the one two-field provider,
1812
+ Trello, where rotating the `token` alone must leave `apiKey` intact — both are
1813
+ required by the REST delivery. An empty `secret` (every field blank) therefore
1814
+ updates the label only.
1815
+
1816
+ When the merge changes the stored payload, `last_synced_at` is reset to `NULL`:
1817
+ the agents still hold the previous value, so the profile reads "synced never"
1818
+ until it is delivered again, and `GET /api/credentials/detect` reports those
1819
+ agents as holding an unrecognised credential for that provider.
1820
+
1765
1821
  ### `DELETE /api/credentials/[id]`
1766
1822
  Delete a credential and its secret permanently.
1767
1823
 
1824
+ **Auth:** browser cookie or bearer token
1825
+
1768
1826
  ### `POST /api/credentials/[id]/sync`
1769
- Sync the credential to all agent containers (or a specified subset).
1827
+ Sync the credential to agent containers.
1828
+
1829
+ **Auth:** browser cookie or bearer token
1770
1830
 
1771
- **Body (optional):** `{ containers?: string[] }` — if omitted or empty, syncs to all agent containers with `AGENT_ID` labels.
1831
+ **Body (optional):** `{ containers?: string[] }` — if omitted, targets every agent container carrying an `AGENT_ID` label.
1772
1832
 
1773
1833
  **Response:** `{ results: { container: string, status: 'ok'|'error', error?: string }[] }`
1774
1834
 
1775
- Before syncing, **all agent containers are cleaned first** (env files, CLI auth, TOOLS.md integration markers removed).
1776
- Only the selected containers receive the new credential and TOOLS.md section.
1777
- This ensures that deselecting an agent or deleting a profile removes the credential from all agents.
1835
+ Each **targeted** container is first cleaned of this provider only — other
1836
+ providers on the same container are left alone — and then receives the new
1837
+ credential and its TOOLS.md row.
1838
+
1839
+ **Containers outside `containers` are not touched.** Syncing is therefore not a
1840
+ way to revoke: dropping an agent from the list leaves whatever it already holds
1841
+ in place. Use `DELETE` below to remove a credential from a container.
1842
+
1843
+ ### `DELETE /api/credentials/[id]/sync`
1844
+ Remove this credential from a single container ("de-sync").
1845
+
1846
+ **Auth:** browser cookie or bearer token
1847
+
1848
+ **Body:** `{ container: string }` — required.
1849
+
1850
+ **Response:** `{ ok: true }`
1851
+
1852
+ Deletes the provider's CLI auth inside that container and drops its TOOLS.md
1853
+ row. `last_synced_at` is deliberately left untouched — it records the last
1854
+ delivery, and a removal is not one — and the audit entry is recorded as
1855
+ `desync`. The profile stays `active`, because de-sync targets one container and
1856
+ the credential may still be delivered to others.
1778
1857
 
1779
1858
  ### `GET /api/credentials/detect`
1780
1859
  Live-detect which credentials are actually present inside agent containers.
1781
1860
 
1861
+ **Auth:** browser cookie or bearer token
1862
+
1782
1863
  **Query params (optional):** `?agent=prometheus` — filter to a single container.
1783
1864
 
1865
+ Costly: one `docker exec` per container, measured at ~6.5s each. Almost all of
1866
+ that is the three CLI calls reaching the network from inside the container, so
1867
+ the figure is largely platform-independent — the `docker exec` itself is ~90ms,
1868
+ and the local file reads another ~100ms. Callers should treat it as a background
1869
+ refresh, not something to block a page render on.
1870
+
1784
1871
  **Response:**
1785
1872
  ```json
1786
1873
  {
@@ -1802,34 +1889,119 @@ Live-detect which credentials are actually present inside agent containers.
1802
1889
  "supabaseCliInstalled": true,
1803
1890
  "trelloLoggedIn": false,
1804
1891
  "trelloCliInstalled": true,
1892
+ "notionLoggedIn": false,
1805
1893
  "toolsMdHasMarkers": true,
1806
1894
  "matchedProfiles": [
1807
1895
  { "profileId": "cred_xxx", "providerId": "github-pat", "label": "GitHub" }
1808
- ]
1896
+ ],
1897
+ "installedProviders": ["github-pat"]
1809
1898
  }
1810
1899
  ]
1811
1900
  }
1812
1901
  ```
1813
1902
 
1814
- Detection runs `docker exec` inside each container:
1815
- - `gh auth status` for GitHub (token read from `~/.config/gh/hosts.yml`)
1816
- - `vercel whoami` for Vercel (token read from `~/.local/share/com.vercel.cli/auth.json`)
1817
- - `supabase projects list --output json` for Supabase (token read from `~/.supabase/access-token`)
1818
- - `test -f ~/.trello-cli/default/config.json` for Trello
1819
- - TOOLS.md markers at `/root/.openclaw/workspace/TOOLS.md`
1903
+ An agent whose probe failed is returned in its place as
1904
+ `{ "container", "agentId", "error" }` — none of the fields above are present, so
1905
+ consumers must check `error` first.
1906
+
1907
+ Each container is read with a **single** `docker exec` running one script that
1908
+ emits NUL-delimited key/value pairs; containers are probed concurrently. The
1909
+ script reads the credential files directly and the parsing happens in
1910
+ JavaScript. Three CLI calls run last and only feed display fields — `githubUser`,
1911
+ `vercelUser`, `vercelTeam`, and the `githubLoggedIn` / `vercelLoggedIn` /
1912
+ `supabaseLoggedIn` flags, along with `supabaseLinked` and `supabaseProjectRef`,
1913
+ which are read only when `supabaseLoggedIn` is true. `supabaseLoggedIn` follows
1914
+ the **exit status** of `supabase projects list`, not the text it prints, so a
1915
+ failure whose wording is unfamiliar still reads as not logged in.
1916
+ `trelloLoggedIn` and `notionLoggedIn` come from the config files instead, and so
1917
+ survive a probe cut short. The three calls reach the network from inside the
1918
+ container, so a completion sentinel is emitted before them: a probe cut short
1919
+ during them still yields valid token matching, with those display fields empty.
1920
+
1921
+ Credential files read: `~/.config/gh/hosts.yml`,
1922
+ `~/.local/share/com.vercel.cli/auth.json` and `config.json`,
1923
+ `~/.supabase/access-token` and `/data/supabase/config.toml`,
1924
+ `~/.config/trello/config.json`, `~/.config/notion/config.json`, plus TOOLS.md
1925
+ markers at `/root/.openclaw/workspace/TOOLS.md`.
1820
1926
 
1821
1927
  `matchedProfiles` is determined by SHA256-hashing the token installed in the container
1822
1928
  and comparing it against the hashed token of each stored profile in the vault.
1823
1929
  Only exact token matches produce a match — no heuristics, no broad profile-level guesses.
1824
1930
 
1931
+ `installedProviders` lists every provider holding a token, matched or not. A
1932
+ provider present there but absent from `matchedProfiles` holds a credential no
1933
+ stored profile accounts for — an edited-but-not-resynced credential, one
1934
+ installed by hand, or the remains of a deleted profile.
1935
+
1825
1936
  ### `POST /api/credentials/[id]/reveal`
1826
- Return the raw secret payload. Every call is logged in the audit trail.
1937
+ Return the raw secret payload. Every call is written to `credential_audit_log`, which no route or view reads.
1827
1938
  Use sparingly.
1828
1939
 
1940
+ **Auth:** browser cookie or bearer token
1941
+
1942
+ Secrets are stored **unencrypted** in `credentials.db` — `credential_secrets.payload`
1943
+ holds the raw JSON. Anything that can read that file, or reach this endpoint with a
1944
+ valid session, has the secrets in the clear.
1945
+
1829
1946
  **Response:** `{ secret: Record<string, string> }`
1830
1947
 
1831
1948
  ---
1832
1949
 
1950
+ ## Diagnostics
1951
+
1952
+ ### `GET /api/debug`
1953
+ Whether the three auth-related environment variables are set. Booleans only —
1954
+ no value, masked or otherwise, is returned.
1955
+
1956
+ **Auth:** browser cookie or bearer token
1957
+
1958
+ **Response:** `{ "hasPassword": true, "hasJwtSecret": true, "hasToken": false }`
1959
+
1960
+ ### `GET /api/envcheck`
1961
+ The process environment, with `REV4A_PASSWORD`, `REV4A_TOKEN`,
1962
+ `REV4A_JWT_SECRET` and `PROVIDER_DEEPSEEK_API_KEY` redacted. Every other
1963
+ variable is returned **in the clear**, so treat the response as sensitive.
1964
+
1965
+ **Auth:** browser cookie or bearer token
1966
+
1967
+ ### `GET /api/provider-usage`
1968
+ Usage and quota state per provider, including live OpenRouter figures.
1969
+
1970
+ **Auth:** browser cookie or bearer token
1971
+
1972
+ **Response:** `{ "providers": [...], "openrouterLive": {…}, "quotas": {…} }`
1973
+
1974
+ ### `GET /api/update-check`
1975
+ Installed version against the latest published one.
1976
+
1977
+ **Auth:** browser cookie or bearer token
1978
+
1979
+ **Query params:** `check=1` queries the registry; without it the answer comes
1980
+ from local state only.
1981
+
1982
+ **Response:** `{ "installed": "1.1.15", "latest": "1.1.16", "updateAvailable": true, "package": "@flame0510/project-aether" }`
1983
+
1984
+ ### `POST /api/update-check`
1985
+ Run `rev4a update` as a detached background process. The dashboard restarts
1986
+ itself as part of the update, so the connection drops.
1987
+
1988
+ **Auth:** browser cookie or bearer token
1989
+
1990
+ ### `GET /api/alerts/smoke`
1991
+ Send a test alert, to verify the Telegram alerting path end to end.
1992
+
1993
+ **Auth:** browser cookie or bearer token
1994
+
1995
+ **Query params:** `stale=0` skips the staleness check.
1996
+
1997
+ **Response:** `{ "ok": true, "stale": true, "result": {…}, "dryRun": false }` —
1998
+ `dryRun` is `true` when `REV4A_TELEGRAM_BOT_TOKEN` or `REV4A_TELEGRAM_CHAT_ID`
1999
+ is unset, meaning nothing was actually sent.
2000
+
2001
+ **Errors:** `403` when smoke alerts are disabled.
2002
+
2003
+ ---
2004
+
1833
2005
  ## Error Format
1834
2006
 
1835
2007
  All errors return JSON: