Help center
Support tickets through the API.
Support tickets and feature requests, addressed by their public short_id. Ticket creation is web-app-only — the API reads, replies, and closes what exists.
List tickets
Your tickets, most recently updated first (complete list — no pagination), each with the last message preview and unread count:
{
"data": {
"items": [
{
"short_id": "ab12cd34",
"category": "bug_report",
"subject": "Briefing didn't arrive",
"status": "open",
"created_at": "2026-10-25T09:00:00Z",
"updated_at": "2026-10-26T08:00:00Z",
"last_message": { "text": "Thanks — looking into it.", "sender": "support", "sent_at": "2026-10-26T08:00:00Z" },
"unread_count": 1
}
]
},
"request_id": "5f0c…"
}
last_message.sender is you or support; unread_count counts unread support replies. last_message is absent on a ticket with no replies yet.
Read a ticket
Full thread — messages oldest first, with sender roles, timestamps, read flags, closed_at when resolved, and attachments as freshly minted short-lived URLs (15 minutes — re-fetch the thread for new ones; never store them). Messages whose send had rejected files also carry failed_attachments (persisted name + reason pairs — the message itself sent):
{
"data": {
"short_id": "ab12cd34",
"category": "bug_report",
"subject": "Briefing didn't arrive",
"status": "open",
"created_at": "2026-10-25T09:00:00Z",
"updated_at": "2026-10-26T08:00:00Z",
"messages": [
{ "sender": "you", "body": "My Monday briefing never arrived.", "sent_at": "2026-10-25T09:00:00Z", "read": true, "attachments": [] },
{ "sender": "support", "body": "Here's what we found —", "sent_at": "2026-10-26T08:00:00Z", "read": false, "attachments": [ { "name": "screenshot.png", "url": "https://…supabase.co/storage/v1/object/sign/support-attachments/…?token=…" } ] }
]
},
"request_id": "5f0c…"
}
Reply — with files, in one request
The reply action carries the message text and up to 5 files in one multipart/form-data request — the service validates and uploads them for you; no separate upload step exists. Files: ≤5 per message, 8MB total across the message, types png/jpeg/gif/webp/pdf/txt/zip — validated by content, not extension.
10 messages / 10 minutes per user, across all tickets — 429 rate_limit.support_messages with Retry-After when you hit it. This includes ticket creation (web-app side); if a ticket's first message would exceed the cap, creation itself is refused with the same error — retry in a minute.
Request (multipart)
| Part | Type | Rules |
|---|---|---|
action | field | reply |
message | field | 1–5000 characters after trim |
files | file ×N | ≤5 files, 8MB total, content-validated |
curl -X POST https://api.daily-tube.com/v1/help/support/tickets/ab12cd34 \
-H "Authorization: Bearer dtk_YOUR_KEY" \
-F action=reply \
-F message="Still seeing this after the restart." \
-F "[email protected]"
const form = new FormData();
form.set("action", "reply");
form.set("message", "Still seeing this after the restart.");
form.append("files", fileInput.files[0]);
const res = await fetch("https://api.daily-tube.com/v1/help/support/tickets/ab12cd34", {
method: "POST",
headers: { Authorization: "Bearer dtk_YOUR_KEY" },
body: form,
});
const { data } = await res.json();
console.log(data.failed_attachments); // any files that didn't make it
import requests
res = requests.post(
"https://api.daily-tube.com/v1/help/support/tickets/ab12cd34",
headers={"Authorization": "Bearer dtk_YOUR_KEY"},
data={"action": "reply", "message": "Still seeing this after the restart."},
files={"files": ("screenshot.png", open("screenshot.png", "rb"), "image/png")},
)
print(res.json()["data"])
Response — always sends, failures reported
The message sends even if some files fail validation — the response lists them:
{
"data": {
"message": { "sender": "you", "body": "Still seeing this after the restart.", "sent_at": "2026-10-26T10:00:00Z", "read": false },
"ticket": { "short_id": "ab12cd34", "status": "open", "updated_at": "2026-10-26T10:00:00Z" },
"failed_attachments": [ { "name": "dump.zip", "reason": "Attachment budget exceeded (8MB total per message)" } ]
},
"request_id": "5f0c…"
}
A JSON-only variant exists for text-only replies or referencing files your account already sent: pass attachment_paths (paths under your own attachment folder — the server scopes them to you; foreign paths are rejected) instead of files. More than 5 distinct declared paths is a hard 400 attachments.too_many.
Other ticket actions
Same endpoint, JSON:
| Action | Body | Effect | Response |
|---|---|---|---|
mark-read | { "action": "mark-read" } | Clears the unread flag on support replies | 200 — { ticket } |
close | { "action": "close" } | Closes the ticket | 200 — { ticket } (with closed_at) |
Feature requests
GET /help/feature-requests— your submissions, newest first:itemsof{ short_id, i_want, so_that, status, created_at }.GET /help/feature-requests/{short_id}— one request in the same shape.POST /help/feature-requests/{short_id}—{ "action": "withdraw" }only (open requests;400 help.request_not_withdrawableotherwise) →200 { short_id, status: "withdrawn" }.
Feature-request creation and discussion happen in the web app — the API exposes listing and withdrawal only.
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": "help.ticket_closed", "message": "Cannot add messages to a closed ticket" },
"request_id": "5f0c…"
}
| Code | Status | When |
|---|---|---|
help.ticket_not_found / request_not_found | 404 | Foreign or unknown id |
help.ticket_closed | 400 | Reply or close on a closed ticket |
help.request_not_withdrawable | 400 | Withdraw on a non-open request |
attachments.invalid | 400 | Malformed attachment paths; request over the 8MB budget |
attachments.too_many | 400 | More than 5 distinct declared paths |
rate_limit.support_messages | 429 | 10 messages / 10 min per user (+ Retry-After) |
validation.invalid_input | 400 | Unknown action; empty/oversized message |
Every keyed endpoint on this page shares the per-key budget — Rate limits.