Channels
Manage delivery channels and verification.
Delivery channels carry your briefings: email, telegram, and webhook. (Web push exists in the product but is not addable through the API.)
List channels
Complete list, newest first, no pagination. GET /manage/channels/{id} returns one (404 channel.not_found for foreign or unknown ids).
Response
The projection depends on the type — emails are masked, webhooks show the URL:
{
"data": {
"items": [
{
"id": "c91d…",
"type": "email",
"status": "pending_verification",
"added_at": "2026-10-26T10:00:00Z",
"identifier": "a***@example.com",
"stage": "code_sent",
"expires_at": "2026-10-26T11:00:00Z"
}
]
},
"request_id": "5f0c…"
}
{
"data": {
"items": [
{ "id": "c44f…", "type": "webhook", "status": "active", "added_at": "2026-10-26T10:00:00Z", "identifier": "https://example.com/hooks/dailytube", "verified_at": "2026-10-26T10:05:00Z" }
]
},
"request_id": "5f0c…"
}
status runs pending_verification → active / disabled. Telegram channels carry identifier (@username, once known) and bot_username; pending channels also carry stage and expires_at.
Add a channel
Request body
A type discriminator plus a nested object of the same name:
| Type | Body | Born as | Verification |
|---|---|---|---|
email | { "type": "email", "email": { "address": "[email protected]" } } | pending_verification | A code arrives by email — submit it with the verify action |
telegram | { "type": "telegram", "telegram": {} } | pending_verification | The response carries a verification_code and bot_username — send the code to the bot in Telegram, then reply YES |
webhook | { "type": "webhook", "webhook": { "url": "https://…" } } | pending_verification | test action: DailyTube POSTs a sample payload; any 2xx verifies |
Request
curl -X POST https://api.daily-tube.com/v1/manage/channels \
-H "Authorization: Bearer dtk_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"type":"webhook","webhook":{"url":"https://example.com/hooks/dailytube"}}'
const res = await fetch("https://api.daily-tube.com/v1/manage/channels", {
method: "POST",
headers: { Authorization: "Bearer dtk_YOUR_KEY", "Content-Type": "application/json" },
body: JSON.stringify({ type: "webhook", webhook: { url: "https://example.com/hooks/dailytube" } }),
});
import urllib.request, json
body = json.dumps({"type": "webhook", "webhook": {"url": "https://example.com/hooks/dailytube"}}).encode()
req = urllib.request.Request(
"https://api.daily-tube.com/v1/manage/channels",
data=body,
headers={"Authorization": "Bearer dtk_YOUR_KEY", "Content-Type": "application/json"},
)
with urllib.request.urlopen(req) as r:
print(r.status)
Response
201 with the pending channel. A telegram add returns the fresh verification_code alongside the projection — that code is the caller's own handshake secret; keep it only long enough to send it to the bot. Adding a webhook when one already exists replaces it (a 200 instead of 201).
Webhook URLs must be https, credential-free, and ≤ 2048 characters, and must resolve to a public address — private/reserved ranges and single-label hostnames are rejected (SSRF-guarded).
Channel actions
Request body
| Action | Body | Effect | Response |
|---|---|---|---|
verify | { "action": "verify", "code": "…" } | Email: code is the token from the emailed link — verifies and returns the channel. Telegram: code is the DT-code — accepted codes still return 400 channel.verification_pending with guidance, because the YES-reply happens in Telegram; poll the channel until active. Already-verified channels return their current state | 200 channel |
resend-verification | { "action": "resend-verification" } | Email only, once per 5 minutes (429 rate_limit.resend_window with the exact wait); telegram resends aren't gated and return a fresh verification_code | 202 channel |
test | { "action": "test" } | Webhook only — POSTs a sample payload (10 s timeout). Any 2xx marks the channel verified and attempts activation | 200 — { success, status_code, verified } |
Response — test
{
"data": { "success": true, "status_code": 200, "verified": true },
"request_id": "5f0c…"
}
Update
One field per call — status (active / disabled) for any type, or url for webhooks (same validation as add):
curl -X PATCH https://api.daily-tube.com/v1/manage/channels/c44f… \
-H "Authorization: Bearer dtk_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"status":"active"}'
200 with the updated channel. Status toggling requires a verified channel (400 channel.not_verified); web-push channels aren't patchable through the API.
Delete (reset)
Resets the channel — the row is removed regardless of status (the web app's own reset behavior). 204, no body.
curl -X DELETE https://api.daily-tube.com/v1/manage/channels/c44f… \
-H "Authorization: Bearer dtk_YOUR_KEY"
Errors
Every error returns the same body — code is the stable machine string, message is human: The full registry of every code lives on Errors.
{
"error": { "code": "channel.invalid_url", "message": "Invalid webhook URL" },
"request_id": "5f0c…"
}
| Code | Status | When |
|---|---|---|
channel.invalid_email / email_in_use | 400 | Email add/resend — bad address, or active on another account |
channel.invalid_url | 400 | Webhook URL rules (https, host, no credentials, ≤2048, SSRF) |
channel.invalid_code | 400 | Wrong/expired verification code, or nothing pending |
channel.verification_pending | 400 | Telegram handshake incomplete — finish it in the bot |
channel.action_not_supported | 400 | e.g. test on a non-webhook channel, verify on a webhook |
channel.invalid_status / invalid_transition / not_verified / not_patchable / not_pending | 400 | PATCH/action preconditions |
channel.type_not_in_plan / limit_reached | 403 | Plan gating |
channel.webhook_test_failed | 400 | Test delivery failed (unreachable / non-2xx / timeout) |
channel.not_found | 404 | Foreign or unknown channel |
Every keyed endpoint on this page shares the per-key budget — Rate limits.