Channels
Workspace-scoped CRUD over channel bindings — Telegram, Slack, Discord, and Web. Each binding stores the protocol-specific secret blob (encrypted at rest with the Fernet key from BIGWAVE_CHANNEL_SECRET_KEY) and pairs it with a default model, default knowledge-base id, and an optional system prompt. Webhook deliveries from third-party providers land at /admin/webhooks and resolve back through the binding row written here.
The router lives at packages/channels/admin_routes.py.
Endpoints
| Method | Path | Auth | Summary |
|---|---|---|---|
| POST | /admin/channels | Bearer | Register a new binding for the calling workspace. |
| GET | /admin/channels | Bearer | List workspace bindings; optional protocol filter. |
| GET | /admin/channels/{binding_id} | Bearer | Fetch one binding (404 cross-tenant). |
| DELETE | /admin/channels/{binding_id} | Bearer | Drop a binding and its credential row. |
| POST | /admin/channels/{binding_id}:rotate | Bearer | Replace the secrets behind an existing binding. |
| POST | /admin/channels/{binding_id}:test | Bearer | Drive a live identity probe (getMe for Telegram). |
POST /admin/channels
Body schema: CreateChannelBindingRequest (packages/channels/admin_routes.py:78).
{
"protocol": "telegram",
"secrets": { "bot_token": "12345:abc…", "webhook_secret": "rand…" },
"system_prompt": "You answer support tickets. Stay polite.",
"default_model": "llama3.2:3b",
"default_kb_id": "kb_support_v1"
}protocol is one of telegram, slack, discord, web. secrets is an opaque dict — the protocol-specific adapter is the only consumer that interprets it. secrets MUST be non-empty (422 otherwise).
Returns 201 with a ChannelBinding (packages/channels/types.py:130):
{
"binding_id": "bnd_01HF…",
"workspace_id": "default",
"protocol": "telegram",
"state": "enabled",
"credentials_ref": "cred_01HF…",
"system_prompt": "…",
"default_model": "llama3.2:3b",
"default_kb_id": "kb_support_v1",
"created_at": "2025-09-01T12:34:56Z",
"last_error": null
}curl -X POST -H "Authorization: Bearer $BIGWAVE_ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"protocol":"telegram","secrets":{"bot_token":"12345:abc"}}' \
http://localhost:8000/admin/channelsGET /admin/channels
Returns a JSON array of ChannelBinding. Pass ?protocol=slack to filter.
GET /admin/channels/{binding_id}
Returns one ChannelBinding. 404 on cross-tenant lookups (no oracle).
DELETE /admin/channels/{binding_id}
Drops the binding row and its underlying credential row. Returns 204. Order matters: the binding is deleted first, then the credential — the schema does not cascade.
POST /admin/channels/{binding_id}:rotate
Body schema: RotateChannelBindingRequest (packages/channels/admin_routes.py:97).
{ "secrets": { "bot_token": "new_token", "webhook_secret": "new_secret" } }Replaces the encrypted credential blob in place. The binding_id, workspace_id, and other binding fields are unchanged. Returns the rotated ChannelBinding. secrets MUST be non-empty (422).
POST /admin/channels/{binding_id}:test
Drives a live identity probe. Telegram bindings call getMe and return the bot username on success (TestConnectionResponse, packages/channels/admin_routes.py:105):
{
"binding_id": "bnd_01HF…",
"protocol": "telegram",
"ok": true,
"detail": "@bigwave_support_bot"
}Slack and Discord adapters have not landed yet; their bindings return ok=false, detail="<protocol> test-connection not implemented yet".
On Telegram failure the binding moves to state=ERRORED and last_error is populated, so the next list / fetch surfaces the diagnostic without re-running the probe.
Operator notes
BIGWAVE_CHANNEL_SECRET_KEYrequired. Without the Fernet key the lifespan does not wire the credential store, and every route here returns503. Generate one with:bashpython -c 'from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())'- Workspace fence. Bindings are workspace-scoped; the request body's
workspace_id(when present) is ignored — the bearer principal is authoritative. See PRD 12 FR-7 pattern. - Auto-bind interaction. When the lifespan auto-binds Telegram or Slack (because
BIGWAVE_CHANNELSlists them), the bound row usesworkspace=defaultand is reused idempotently across restarts. Per-workspace bindings created here are independent. - Audit trail.
create,rotate, anddeletewrite toaudit_logviaAuditLogRepositorywhen one is wired. secretspayload shape. Telegram expects{"bot_token": "…"}and an optional{"webhook_secret": "…"}for thesetWebhook(secret_token=…)echo. Slack expects{"bot_token": "xoxb-…", "signing_secret": "…"}.