Skip to content

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 ​

MethodPathAuthSummary
POST/admin/channelsBearerRegister a new binding for the calling workspace.
GET/admin/channelsBearerList workspace bindings; optional protocol filter.
GET/admin/channels/{binding_id}BearerFetch one binding (404 cross-tenant).
DELETE/admin/channels/{binding_id}BearerDrop a binding and its credential row.
POST/admin/channels/{binding_id}:rotateBearerReplace the secrets behind an existing binding.
POST/admin/channels/{binding_id}:testBearerDrive a live identity probe (getMe for Telegram).

POST /admin/channels ​

Body schema: CreateChannelBindingRequest (packages/channels/admin_routes.py:78).

json
{
  "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):

json
{
  "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
}
bash
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/channels

GET /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).

json
{ "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):

json
{
  "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_KEY required. Without the Fernet key the lifespan does not wire the credential store, and every route here returns 503. Generate one with:
    bash
    python -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_CHANNELS lists them), the bound row uses workspace=default and is reused idempotently across restarts. Per-workspace bindings created here are independent.
  • Audit trail. create, rotate, and delete write to audit_log via AuditLogRepository when one is wired.
  • secrets payload shape. Telegram expects {"bot_token": "…"} and an optional {"webhook_secret": "…"} for the setWebhook(secret_token=…) echo. Slack expects {"bot_token": "xoxb-…", "signing_secret": "…"}.

Dual-licensed: MIT OR Apache-2.0