| 1 | # API And WebUI |
| 2 | |
| 3 | ## Source Anchors |
| 4 | |
| 5 | - HTTP handler base and route registration: `/a0/helpers/api.py` |
| 6 | - API DOX: `/a0/api/AGENTS.md` |
| 7 | - WebSocket handler base: `/a0/helpers/ws.py` |
| 8 | - WebUI shell DOX: `/a0/webui/AGENTS.md` |
| 9 | - Component and JS DOX: `/a0/webui/components/AGENTS.md`, `/a0/webui/js/AGENTS.md` |
| 10 | - Frontend extension loading: `/a0/webui/js/extensions.js` |
| 11 | |
| 12 | ## HTTP API Contract |
| 13 | |
| 14 | HTTP API handlers derive from `helpers.api.ApiHandler`: |
| 15 | |
| 16 | ```python |
| 17 | from flask import Request, Response |
| 18 | from helpers.api import ApiHandler |
| 19 | |
| 20 | class MyEndpoint(ApiHandler): |
| 21 | @classmethod |
| 22 | def get_methods(cls) -> list[str]: |
| 23 | return ["POST"] |
| 24 | |
| 25 | async def process(self, input: dict, request: Request) -> dict | Response: |
| 26 | return {"ok": True} |
| 27 | ``` |
| 28 | |
| 29 | Defaults from `ApiHandler`: |
| 30 | |
| 31 | | Method | Default | |
| 32 | |---|---| |
| 33 | | `requires_loopback()` | `False` | |
| 34 | | `requires_api_key()` | `False` | |
| 35 | | `requires_auth()` | `True` | |
| 36 | | `get_methods()` | `["POST"]` | |
| 37 | | `requires_csrf()` | `requires_auth()` | |
| 38 | |
| 39 | Override these only when the endpoint contract requires it. Keep auth and CSRF protection intact for browser-facing state changes. |
| 40 | |
| 41 | ## API Routes |
| 42 | |
| 43 | `helpers.api.register_api_route(...)` registers: |
| 44 | |
| 45 | ```text |
| 46 | /api/<path:path> |
| 47 | ``` |
| 48 | |
| 49 | Resolution: |
| 50 | |
| 51 | - Built-in endpoint `api/<name>.py` becomes `/api/<name>`. |
| 52 | - Plugin endpoint `plugins/<plugin>/api/<handler>.py` becomes `/api/plugins/<plugin>/<handler>`. |
| 53 | |
| 54 | Return a Flask `Response` for files, redirects, custom status codes, and plain text. Return a dictionary for JSON success payloads. |
| 55 | |
| 56 | Direct files under `api/*.py` require matching `api/*.py.dox.md`. |
| 57 | |
| 58 | ## WebSocket Handlers |
| 59 | |
| 60 | WebSocket handlers live in `api/ws_*.py` or plugin API folders and derive from `helpers.ws.WsHandler`: |
| 61 | |
| 62 | ```python |
| 63 | from helpers.ws import WsHandler |
| 64 | |
| 65 | class MyHandler(WsHandler): |
| 66 | async def process(self, event: str, data: dict, sid: str) -> dict | None: |
| 67 | return {"ok": True} |
| 68 | ``` |
| 69 | |
| 70 | `WsHandler` mirrors `ApiHandler` security flags: auth defaults to `True`, CSRF defaults to auth, API-key and loopback default to `False`. Handlers should validate event data before using it and avoid returning secrets or unfiltered exception details. |
| 71 | |
| 72 | ## WebUI Work |
| 73 | |
| 74 | Follow the nearest WebUI DOX before changing frontend files: |
| 75 | |
| 76 | - `webui/AGENTS.md` for the shell, CSS, assets, vendor, and extension loader. |
| 77 | - `webui/js/AGENTS.md` for stores, modals, API helpers, and JS infrastructure. |
| 78 | - `webui/components/AGENTS.md` and child docs for Alpine components. |
| 79 | |
| 80 | Patterns: |
| 81 | |
| 82 | - Store-dependent content should be gated by a `template x-if` guard before using `$store.<name>`. |
| 83 | - Stores are registered with `createStore` from `/js/AlpineStore.js`. |
| 84 | - Modals use `openModal(path)` and `closeModal()` from `/js/modals.js`. |
| 85 | - Plugin settings UIs bind persisted plugin values to `config.*` and modal-only state/actions to `context.*`. |
| 86 | - Plugin UI should use the A0 notification system instead of inline success/error boxes. |
| 87 | |
| 88 | ## Verification |
| 89 | |
| 90 | - Run endpoint-specific tests or nearest API/WebSocket tests for handler behavior. |
| 91 | - For auth, CSRF, upload/download, tunnel, or file endpoints, run security-focused regressions. |
| 92 | - For WebUI changes, use targeted component/store tests or a browser smoke check when practical. |
| 93 | - Check file-level DOX coverage when touching direct `api/*.py` modules. |