Three endpoints: list, read one, and act on one. Every response is owner-scoped — another account's briefing is indistinguishable from a nonexistent one.
## List summaries
```endpoint
GET /summaries
```
Returns the account's briefings, newest expected edition first (when filtering by saved, newest saved first). Search results are additionally capped at 50 matches.
### Query parameters
| Parameter | Type | Rules |
|---|---|---|
| `q` | string | Optional. Full-text search across headline, dek, and story text; non-empty after trim, ≤ 200 chars |
| `is_saved` | boolean | Optional tri-state — `true` / `false` only (anything else is `400`) |
| `is_shared` | boolean | Optional — `true` returns shared briefings, `false` unshared |
| `expectedAfter` | timestamp | Optional. Window start (inclusive), RFC 3339 |
| `expectedBefore` | timestamp | Optional. Window end (inclusive); must be ≥ `expectedAfter` |
| `order` | enum | `date` (default) or `relevance` — `relevance` requires `q` |
| `maxResults` | integer | 1–100, default 20 |
| `page` | integer | 1-based; `0` and negatives are valid and return an empty `200` — see [Pagination](/docs/pagination) |
### Request
```tabs
~~~curl
curl "https://api.daily-tube.com/v1/summaries?q=telescope&maxResults=5" \
-H "Authorization: Bearer dtk_YOUR_KEY"
~~~
~~~javascript
const url = new URL("https://api.daily-tube.com/v1/summaries");
url.searchParams.set("q", "telescope");
url.searchParams.set("maxResults", "5");
const res = await fetch(url, { headers: { Authorization: "Bearer dtk_YOUR_KEY" } });
~~~
~~~python
import urllib.request, json, urllib.parse
url = "https://api.daily-tube.com/v1/summaries?" + urllib.parse.urlencode({"q": "telescope", "maxResults": 5})
req = urllib.request.Request(url, headers={"Authorization": "Bearer dtk_YOUR_KEY"})
with urllib.request.urlopen(req) as r:
print(json.load(r)["data"]["items"])
~~~
```
### Response
```responses
~~~200 OK
{
"data": {
"items": [
{
"id": "7f3a…91",
"expected_datetime": "2026-10-25T14:00:00Z",
"created_at": "2026-10-25T14:02:11Z",
"headline": "Space digest weekly",
"dek": "Orbital roundup",
"language": "english",
"section_count": 2,
"success": true,
"is_saved": true,
"is_shared": true,
"short_id": "a1b2c3d4",
"match_field": "story"
}
],
"pageInfo": { "totalResults": 3, "resultsPerPage": 5, "page": 1, "totalPages": 1 },
"searchInfo": { "suggestions": [ { "query": "telescopes", "result_count": 2, "summary_ids": ["7f3a…91"] } ] }
},
"request_id": "5f0c…"
}
~~~
```
- `match_field` appears **only** when `q` is set — where the match was found: `headline`, `dek`, or `story`.
- `searchInfo.suggestions` appears only on **zero-match** searches: up to 3 near-miss queries that do have results.
- With `q`, results are capped at 50 by the search window; without `q`, the full account list is paged exactly.
## Get a summary
```endpoint
GET /summaries/{id}
```
### Path parameter
| Parameter | Type | Rules |
|---|---|---|
| `id` | string | **Required.** One of three kinds — see below. Malformed ids are `400`; foreign or unknown ids are a uniform `404 summary.not_found` |
- **Internal id** (keyed) — the owner's full projection below. The first owner read of a completed briefing marks it read, and if delivery hasn't started yet, fires it.
- **Share id** (`short_id`, 8-hex) — works with or without a key: your own briefing gives the owner view, another account's gives the public read-only projection (the `/s/{short_id}` page's data, with a coarse view count).
- **Keyless-run id** (bare 10-hex, from keyless [Summarize](/docs/summarize)) — content and state only: `progress: { overall_percent }` while running, a compact completed body (with `progress: { sources_total, sources_read }`), or `state: "failed"`.
### Response
The owner projection varies by lifecycle state — all three are `200`:
```responses
~~~200 · completed
{
"data": {
"id": "7f3a…91",
"state": "completed",
"expected_datetime": "2026-10-25T14:00:00Z",
"created_at": "2026-10-25T14:02:11Z",
"language": "english",
"trigger_type": "automatic",
"headline": "Space digest weekly",
"dek": "Orbital roundup",
"tags": ["space"],
"plain_text": "# Space digest weekly\n\n…",
"sections": [
{
"emoji": "🛰",
"category": "News",
"summary": "…",
"importance_score": 4,
"plain_text": "…",
"sources": [ { "content_id": "…", "content_title": "…", "source_type": "youtube", "included": true } ]
}
],
"delivery": {
"delivered_at": "2026-10-25T14:05:00Z",
"channels": [ { "type": "email", "success": true } ]
},
"share": { "is_shared": true, "short_id": "a1b2c3d4", "url": "https://daily-tube.com/s/a1b2c3d4", "view_count": 12 },
"is_saved": true
},
"request_id": "5f0c…"
}
~~~
~~~200 · in progress
{
"data": {
"id": "7f3a…91",
"state": "in_progress",
"started_at": "2026-10-26T09:00:00Z",
"progress": {
"overall_percent": 55,
"retrieval": { "status": "completed", "percent": 100 },
"generation": { "status": "stage4", "percent": 10, "label": "writing" }
}
},
"request_id": "5f0c…"
}
~~~
~~~200 · skipped or failed
{
"data": {
"id": "7f3a…91",
"state": "skipped_no_new_content",
"reason": "No new content since your last briefing",
"finished_at": "2026-10-25T14:01:00Z",
"delivery": { "delivered_at": "2026-10-25T14:05:00Z", "channels": [] }
},
"request_id": "5f0c…"
}
~~~
```
- `state` on the terminal shape: `skipped_no_new_content`, `skipped_no_sources`, `skipped_no_feeds`, `skipped_insufficient_content`, or `failed` — each with its human `reason`.
- `progress.generation.label` is the coarse stage word (`starting`, `reading`, `writing`, `finalizing`, `done`); the raw stage strings stay in `status`.
- `share.url`/`share.short_id` appear only while shared; `view_count` is the coarse daily-deduped count.
## Summary actions
```endpoint
POST /summaries/{id}
```
Body: `{ "action": "…" }` — the same internal-or-share id kinds as the read.
| Action | Body | Effect | Response |
|---|---|---|---|
| `share` | `{ "action": "share" }` | Creates (first time) or reuses the 8-hex `short_id` and marks shared | `200` — `{ short_id, url, is_shared, reused }`; first share returns `reused: false`, repeats return the same id with `reused: true` |
| `unshare` | `{ "action": "unshare" }` | Marks unshared, **keeps** the `short_id` | `200` — `{ short_id, is_shared: false }` |
| `save` / `unsave` | `{ "action": "save" }` | Toggles the saved (Library) stamp | `200` — `{ id, is_saved }`; idempotent |
| `send-early` | `{ "action": "send-early" }` | Dispatches delivery now | `202` — `{ summary_id, dispatched: true }` |
`share` requires a completed, successful briefing (`400 summary.not_completed`).
### Request — share
```tabs
~~~curl
curl -X POST https://api.daily-tube.com/v1/summaries/7f3a…91 \
-H "Authorization: Bearer dtk_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"action":"share"}'
~~~
~~~javascript
const res = await fetch("https://api.daily-tube.com/v1/summaries/7f3a…91", {
method: "POST",
headers: { Authorization: "Bearer dtk_YOUR_KEY", "Content-Type": "application/json" },
body: JSON.stringify({ action: "share" }),
});
const { data } = await res.json();
console.log(data.url); // the public /s/{short_id} link
~~~
~~~python
import urllib.request, json
body = json.dumps({"action": "share"}).encode()
req = urllib.request.Request(
"https://api.daily-tube.com/v1/summaries/7f3a…91",
data=body,
headers={"Authorization": "Bearer dtk_YOUR_KEY", "Content-Type": "application/json"},
)
with urllib.request.urlopen(req) as r:
print(json.load(r)["data"])
~~~
```
### Response — share
```responses
~~~200 OK
{
"data": { "short_id": "a1b2c3d4", "url": "https://daily-tube.com/s/a1b2c3d4", "is_shared": true, "reused": false },
"request_id": "5f0c…"
}
~~~
```
## Errors
Every error returns the same body — `code` is the stable machine string, `message` is human:
```responses
~~~404 Not Found
{
"error": { "code": "summary.not_found", "message": "Summary not found" },
"request_id": "5f0c…"
}
~~~
```
| Code | Status | When |
|---|---|---|
| `summary.not_found` | 404 | Foreign or unknown briefing — uniform across all id kinds |
| `summary.not_completed` / `not_saveable` / `not_shared` | 400 | Action preconditions |
| `summary.already_sent` / `delivery_in_progress` | 409 | send-early on a delivered / mid-flight briefing |
| `summary.not_automatic` / `not_ready` | 422 | send-early on a non-automatic or still-generating briefing |
| `summary.dispatch_failed` | 502 | The generation workflow failed to dispatch |
| `validation.invalid_input` | 400 | Unknown action; malformed filters (see [Errors](/docs/errors)) |
> Every keyed endpoint on this page shares the per-key budget — [Rate limits](/docs/rate-limits).
Summaries
Three endpoints: list, read one, and act on one. Every response is owner-scoped — another account's briefing is indistinguishable from a nonexistent one.
List summaries
GET /summaries
Returns the account's briefings, newest expected edition first (when filtering by saved, newest saved first). Search results are additionally capped at 50 matches.
Query parameters
| Parameter | Type | Rules |
|---|---|---|
| q | string | Optional. Full-text search across headline, dek, and story text; non-empty after trim, ≤ 200 chars |
| is_saved | boolean | Optional tri-state — true / false only (anything else is 400) |
| is_shared | boolean | Optional — true returns shared briefings, false unshared |
| expectedAfter | timestamp | Optional. Window start (inclusive), RFC 3339 |
| expectedBefore | timestamp | Optional. Window end (inclusive); must be ≥ expectedAfter |
| order | enum | date (default) or relevance — relevance requires q |
| maxResults | integer | 1–100, default 20 |
| page | integer | 1-based; 0 and negatives are valid and return an empty 200 — see Pagination (/docs/pagination) |
Request
~~~curl
curl "https://api.daily-tube.com/v1/summaries?q=telescope&maxResults=5" \
-H "Authorization: Bearer dtk_YOUR_KEY"
~~~
~~~javascript
const url = new URL("https://api.daily-tube.com/v1/summaries");
url.searchParams.set("q", "telescope");
url.searchParams.set("maxResults", "5");
const res = await fetch(url, { headers: { Authorization: "Bearer dtk_YOUR_KEY" } });
~~~
~~~python
import urllib.request, json, urllib.parse
url = "https://api.daily-tube.com/v1/summaries?" + urllib.parse.urlencode({"q": "telescope", "maxResults": 5})
req = urllib.request.Request(url, headers={"Authorization": "Bearer dtk_YOUR_KEY"})
with urllib.request.urlopen(req) as r:
print(json.load(r)["data"]["items"])
~~~
Response
~~~200 OK
{
"data": {
"items": [
{
"id": "7f3a…91",
"expected_datetime": "2026-10-25T14:00:00Z",
"created_at": "2026-10-25T14:02:11Z",
"headline": "Space digest weekly",
"dek": "Orbital roundup",
"language": "english",
"section_count": 2,
"success": true,
"is_saved": true,
"is_shared": true,
"short_id": "a1b2c3d4",
"match_field": "story"
}
],
"pageInfo": { "totalResults": 3, "resultsPerPage": 5, "page": 1, "totalPages": 1 },
"searchInfo": { "suggestions": [ { "query": "telescopes", "result_count": 2, "summary_ids": ["7f3a…91"] } ] }
},
"request_id": "5f0c…"
}
~~~
• match_field appears only when q is set — where the match was found: headline, dek, or story.
• searchInfo.suggestions appears only on zero-match searches: up to 3 near-miss queries that do have results.
• With q, results are capped at 50 by the search window; without q, the full account list is paged exactly.
Get a summary
GET /summaries/{id}
Path parameter
| Parameter | Type | Rules |
|---|---|---|
| id | string | Required. One of three kinds — see below. Malformed ids are 400; foreign or unknown ids are a uniform 404 summary.not_found |
• Internal id (keyed) — the owner's full projection below. The first owner read of a completed briefing marks it read, and if delivery hasn't started yet, fires it.
• Share id (short_id, 8-hex) — works with or without a key: your own briefing gives the owner view, another account's gives the public read-only projection (the /s/{short_id} page's data, with a coarse view count).
• Keyless-run id (bare 10-hex, from keyless Summarize (/docs/summarize)) — content and state only: progress: { overall_percent } while running, a compact completed body (with progress: { sources_total, sources_read }), or state: "failed".
Response
The owner projection varies by lifecycle state — all three are 200:
~~~200 · completed
{
"data": {
"id": "7f3a…91",
"state": "completed",
"expected_datetime": "2026-10-25T14:00:00Z",
"created_at": "2026-10-25T14:02:11Z",
"language": "english",
"trigger_type": "automatic",
"headline": "Space digest weekly",
"dek": "Orbital roundup",
"tags": ["space"],
"plain_text": "# Space digest weekly\n\n…",
"sections": [
{
"emoji": "🛰",
"category": "News",
"summary": "…",
"importance_score": 4,
"plain_text": "…",
"sources": [ { "content_id": "…", "content_title": "…", "source_type": "youtube", "included": true } ]
}
],
"delivery": {
"delivered_at": "2026-10-25T14:05:00Z",
"channels": [ { "type": "email", "success": true } ]
},
"share": { "is_shared": true, "short_id": "a1b2c3d4", "url": "https://daily-tube.com/s/a1b2c3d4", "view_count": 12 },
"is_saved": true
},
"request_id": "5f0c…"
}
~~~
~~~200 · in progress
{
"data": {
"id": "7f3a…91",
"state": "in_progress",
"started_at": "2026-10-26T09:00:00Z",
"progress": {
"overall_percent": 55,
"retrieval": { "status": "completed", "percent": 100 },
"generation": { "status": "stage4", "percent": 10, "label": "writing" }
}
},
"request_id": "5f0c…"
}
~~~
~~~200 · skipped or failed
{
"data": {
"id": "7f3a…91",
"state": "skipped_no_new_content",
"reason": "No new content since your last briefing",
"finished_at": "2026-10-25T14:01:00Z",
"delivery": { "delivered_at": "2026-10-25T14:05:00Z", "channels": [] }
},
"request_id": "5f0c…"
}
~~~
• state on the terminal shape: skipped_no_new_content, skipped_no_sources, skipped_no_feeds, skipped_insufficient_content, or failed — each with its human reason.
• progress.generation.label is the coarse stage word (starting, reading, writing, finalizing, done); the raw stage strings stay in status.
• share.url/share.short_id appear only while shared; view_count is the coarse daily-deduped count.
Summary actions
POST /summaries/{id}
Body: { "action": "…" } — the same internal-or-share id kinds as the read.
| Action | Body | Effect | Response |
|---|---|---|---|
| share | { "action": "share" } | Creates (first time) or reuses the 8-hex short_id and marks shared | 200 — { short_id, url, is_shared, reused }; first share returns reused: false, repeats return the same id with reused: true |
| unshare | { "action": "unshare" } | Marks unshared, keeps the short_id | 200 — { short_id, is_shared: false } |
| save / unsave | { "action": "save" } | Toggles the saved (Library) stamp | 200 — { id, is_saved }; idempotent |
| send-early | { "action": "send-early" } | Dispatches delivery now | 202 — { summary_id, dispatched: true } |
share requires a completed, successful briefing (400 summary.not_completed).
Request — share
~~~curl
curl -X POST https://api.daily-tube.com/v1/summaries/7f3a…91 \
-H "Authorization: Bearer dtk_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"action":"share"}'
~~~
~~~javascript
const res = await fetch("https://api.daily-tube.com/v1/summaries/7f3a…91", {
method: "POST",
headers: { Authorization: "Bearer dtk_YOUR_KEY", "Content-Type": "application/json" },
body: JSON.stringify({ action: "share" }),
});
const { data } = await res.json();
console.log(data.url); // the public /s/{short_id} link
~~~
~~~python
import urllib.request, json
body = json.dumps({"action": "share"}).encode()
req = urllib.request.Request(
"https://api.daily-tube.com/v1/summaries/7f3a…91",
data=body,
headers={"Authorization": "Bearer dtk_YOUR_KEY", "Content-Type": "application/json"},
)
with urllib.request.urlopen(req) as r:
print(json.load(r)["data"])
~~~
Response — share
~~~200 OK
{
"data": { "short_id": "a1b2c3d4", "url": "https://daily-tube.com/s/a1b2c3d4", "is_shared": true, "reused": false },
"request_id": "5f0c…"
}
~~~
Errors
Every error returns the same body — code is the stable machine string, message is human:
~~~404 Not Found
{
"error": { "code": "summary.not_found", "message": "Summary not found" },
"request_id": "5f0c…"
}
~~~
| Code | Status | When |
|---|---|---|
| summary.not_found | 404 | Foreign or unknown briefing — uniform across all id kinds |
| summary.not_completed / not_saveable / not_shared | 400 | Action preconditions |
| summary.already_sent / delivery_in_progress | 409 | send-early on a delivered / mid-flight briefing |
| summary.not_automatic / not_ready | 422 | send-early on a non-automatic or still-generating briefing |
| summary.dispatch_failed | 502 | The generation workflow failed to dispatch |
| validation.invalid_input | 400 | Unknown action; malformed filters (see Errors (/docs/errors)) |
Every keyed endpoint on this page shares the per-key budget — Rate limits (/docs/rate-limits).
Core / Summaries
Summaries
List, search, and read daily briefings.
Three endpoints: list, read one, and act on one. Every response is owner-scoped — another account's briefing is indistinguishable from a nonexistent one.
List summaries
GET/summariesapi.daily-tube.com/v1
Returns the account's briefings, newest expected edition first (when filtering by saved, newest saved first). Search results are additionally capped at 50 matches.
Query parameters
| Parameter | Type | Rules |
|---|---|---|
q | string | Optional. Full-text search across headline, dek, and story text; non-empty after trim, ≤ 200 chars |
is_saved | boolean | Optional tri-state — true / false only (anything else is 400) |
is_shared | boolean | Optional — true returns shared briefings, false unshared |
expectedAfter | timestamp | Optional. Window start (inclusive), RFC 3339 |
expectedBefore | timestamp | Optional. Window end (inclusive); must be ≥ expectedAfter |
order | enum | date (default) or relevance — relevance requires q |
maxResults | integer | 1–100, default 20 |
page | integer | 1-based; 0 and negatives are valid and return an empty 200 — see Pagination |
Request
curl "https://api.daily-tube.com/v1/summaries?q=telescope&maxResults=5" \
-H "Authorization: Bearer dtk_YOUR_KEY"
curl "https://api.daily-tube.com/v1/summaries?q=telescope&maxResults=5" \
-H "Authorization: Bearer dtk_YOUR_KEY"
const url = new URL("https://api.daily-tube.com/v1/summaries");
url.searchParams.set("q", "telescope");
url.searchParams.set("maxResults", "5");
const res = await fetch(url, { headers: { Authorization: "Bearer dtk_YOUR_KEY" } });
const url = new URL("https://api.daily-tube.com/v1/summaries");
url.searchParams.set("q", "telescope");
url.searchParams.set("maxResults", "5");
const res = await fetch(url, { headers: { Authorization: "Bearer dtk_YOUR_KEY" } });
import urllib.request, json, urllib.parse
url = "https://api.daily-tube.com/v1/summaries?" + urllib.parse.urlencode({"q": "telescope", "maxResults": 5})
req = urllib.request.Request(url, headers={"Authorization": "Bearer dtk_YOUR_KEY"})
with urllib.request.urlopen(req) as r:
print(json.load(r)["data"]["items"])
import urllib.request, json, urllib.parse
url = "https://api.daily-tube.com/v1/summaries?" + urllib.parse.urlencode({"q": "telescope", "maxResults": 5})
req = urllib.request.Request(url, headers={"Authorization": "Bearer dtk_YOUR_KEY"})
with urllib.request.urlopen(req) as r:
print(json.load(r)["data"]["items"])
Response
{
"data": {
"items": [
{
"id": "7f3a…91",
"expected_datetime": "2026-10-25T14:00:00Z",
"created_at": "2026-10-25T14:02:11Z",
"headline": "Space digest weekly",
"dek": "Orbital roundup",
"language": "english",
"section_count": 2,
"success": true,
"is_saved": true,
"is_shared": true,
"short_id": "a1b2c3d4",
"match_field": "story"
}
],
"pageInfo": { "totalResults": 3, "resultsPerPage": 5, "page": 1, "totalPages": 1 },
"searchInfo": { "suggestions": [ { "query": "telescopes", "result_count": 2, "summary_ids": ["7f3a…91"] } ] }
},
"request_id": "5f0c…"
}
{
"data": {
"items": [
{
"id": "7f3a…91",
"expected_datetime": "2026-10-25T14:00:00Z",
"created_at": "2026-10-25T14:02:11Z",
"headline": "Space digest weekly",
"dek": "Orbital roundup",
"language": "english",
"section_count": 2,
"success": true,
"is_saved": true,
"is_shared": true,
"short_id": "a1b2c3d4",
"match_field": "story"
}
],
"pageInfo": { "totalResults": 3, "resultsPerPage": 5, "page": 1, "totalPages": 1 },
"searchInfo": { "suggestions": [ { "query": "telescopes", "result_count": 2, "summary_ids": ["7f3a…91"] } ] }
},
"request_id": "5f0c…"
}
match_fieldappears only whenqis set — where the match was found:headline,dek, orstory.searchInfo.suggestionsappears only on zero-match searches: up to 3 near-miss queries that do have results.- With
q, results are capped at 50 by the search window; withoutq, the full account list is paged exactly.
Get a summary
GET/summaries/{id}api.daily-tube.com/v1
Path parameter
| Parameter | Type | Rules |
|---|---|---|
id | string | Required. One of three kinds — see below. Malformed ids are 400; foreign or unknown ids are a uniform 404 summary.not_found |
- Internal id (keyed) — the owner's full projection below. The first owner read of a completed briefing marks it read, and if delivery hasn't started yet, fires it.
- Share id (
short_id, 8-hex) — works with or without a key: your own briefing gives the owner view, another account's gives the public read-only projection (the/s/{short_id}page's data, with a coarse view count). - Keyless-run id (bare 10-hex, from keyless Summarize) — content and state only:
progress: { overall_percent }while running, a compact completed body (withprogress: { sources_total, sources_read }), orstate: "failed".
Response
The owner projection varies by lifecycle state — all three are 200:
{
"data": {
"id": "7f3a…91",
"state": "completed",
"expected_datetime": "2026-10-25T14:00:00Z",
"created_at": "2026-10-25T14:02:11Z",
"language": "english",
"trigger_type": "automatic",
"headline": "Space digest weekly",
"dek": "Orbital roundup",
"tags": ["space"],
"plain_text": "# Space digest weekly\n\n…",
"sections": [
{
"emoji": "🛰",
"category": "News",
"summary": "…",
"importance_score": 4,
"plain_text": "…",
"sources": [ { "content_id": "…", "content_title": "…", "source_type": "youtube", "included": true } ]
}
],
"delivery": {
"delivered_at": "2026-10-25T14:05:00Z",
"channels": [ { "type": "email", "success": true } ]
},
"share": { "is_shared": true, "short_id": "a1b2c3d4", "url": "https://daily-tube.com/s/a1b2c3d4", "view_count": 12 },
"is_saved": true
},
"request_id": "5f0c…"
}
{
"data": {
"id": "7f3a…91",
"state": "completed",
"expected_datetime": "2026-10-25T14:00:00Z",
"created_at": "2026-10-25T14:02:11Z",
"language": "english",
"trigger_type": "automatic",
"headline": "Space digest weekly",
"dek": "Orbital roundup",
"tags": ["space"],
"plain_text": "# Space digest weekly\n\n…",
"sections": [
{
"emoji": "🛰",
"category": "News",
"summary": "…",
"importance_score": 4,
"plain_text": "…",
"sources": [ { "content_id": "…", "content_title": "…", "source_type": "youtube", "included": true } ]
}
],
"delivery": {
"delivered_at": "2026-10-25T14:05:00Z",
"channels": [ { "type": "email", "success": true } ]
},
"share": { "is_shared": true, "short_id": "a1b2c3d4", "url": "https://daily-tube.com/s/a1b2c3d4", "view_count": 12 },
"is_saved": true
},
"request_id": "5f0c…"
}
{
"data": {
"id": "7f3a…91",
"state": "in_progress",
"started_at": "2026-10-26T09:00:00Z",
"progress": {
"overall_percent": 55,
"retrieval": { "status": "completed", "percent": 100 },
"generation": { "status": "stage4", "percent": 10, "label": "writing" }
}
},
"request_id": "5f0c…"
}
{
"data": {
"id": "7f3a…91",
"state": "in_progress",
"started_at": "2026-10-26T09:00:00Z",
"progress": {
"overall_percent": 55,
"retrieval": { "status": "completed", "percent": 100 },
"generation": { "status": "stage4", "percent": 10, "label": "writing" }
}
},
"request_id": "5f0c…"
}
{
"data": {
"id": "7f3a…91",
"state": "skipped_no_new_content",
"reason": "No new content since your last briefing",
"finished_at": "2026-10-25T14:01:00Z",
"delivery": { "delivered_at": "2026-10-25T14:05:00Z", "channels": [] }
},
"request_id": "5f0c…"
}
{
"data": {
"id": "7f3a…91",
"state": "skipped_no_new_content",
"reason": "No new content since your last briefing",
"finished_at": "2026-10-25T14:01:00Z",
"delivery": { "delivered_at": "2026-10-25T14:05:00Z", "channels": [] }
},
"request_id": "5f0c…"
}
stateon the terminal shape:skipped_no_new_content,skipped_no_sources,skipped_no_feeds,skipped_insufficient_content, orfailed— each with its humanreason.progress.generation.labelis the coarse stage word (starting,reading,writing,finalizing,done); the raw stage strings stay instatus.share.url/share.short_idappear only while shared;view_countis the coarse daily-deduped count.
Summary actions
POST/summaries/{id}api.daily-tube.com/v1
Body: { "action": "…" } — the same internal-or-share id kinds as the read.
| Action | Body | Effect | Response |
|---|---|---|---|
share | { "action": "share" } | Creates (first time) or reuses the 8-hex short_id and marks shared | 200 — { short_id, url, is_shared, reused }; first share returns reused: false, repeats return the same id with reused: true |
unshare | { "action": "unshare" } | Marks unshared, keeps the short_id | 200 — { short_id, is_shared: false } |
save / unsave | { "action": "save" } | Toggles the saved (Library) stamp | 200 — { id, is_saved }; idempotent |
send-early | { "action": "send-early" } | Dispatches delivery now | 202 — { summary_id, dispatched: true } |
share requires a completed, successful briefing (400 summary.not_completed).
Request — share
curl -X POST https://api.daily-tube.com/v1/summaries/7f3a…91 \
-H "Authorization: Bearer dtk_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"action":"share"}'
curl -X POST https://api.daily-tube.com/v1/summaries/7f3a…91 \
-H "Authorization: Bearer dtk_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"action":"share"}'
const res = await fetch("https://api.daily-tube.com/v1/summaries/7f3a…91", {
method: "POST",
headers: { Authorization: "Bearer dtk_YOUR_KEY", "Content-Type": "application/json" },
body: JSON.stringify({ action: "share" }),
});
const { data } = await res.json();
console.log(data.url); // the public /s/{short_id} link
const res = await fetch("https://api.daily-tube.com/v1/summaries/7f3a…91", {
method: "POST",
headers: { Authorization: "Bearer dtk_YOUR_KEY", "Content-Type": "application/json" },
body: JSON.stringify({ action: "share" }),
});
const { data } = await res.json();
console.log(data.url); // the public /s/{short_id} link
import urllib.request, json
body = json.dumps({"action": "share"}).encode()
req = urllib.request.Request(
"https://api.daily-tube.com/v1/summaries/7f3a…91",
data=body,
headers={"Authorization": "Bearer dtk_YOUR_KEY", "Content-Type": "application/json"},
)
with urllib.request.urlopen(req) as r:
print(json.load(r)["data"])
import urllib.request, json
body = json.dumps({"action": "share"}).encode()
req = urllib.request.Request(
"https://api.daily-tube.com/v1/summaries/7f3a…91",
data=body,
headers={"Authorization": "Bearer dtk_YOUR_KEY", "Content-Type": "application/json"},
)
with urllib.request.urlopen(req) as r:
print(json.load(r)["data"])
Response — share
{
"data": { "short_id": "a1b2c3d4", "url": "https://daily-tube.com/s/a1b2c3d4", "is_shared": true, "reused": false },
"request_id": "5f0c…"
}
{
"data": { "short_id": "a1b2c3d4", "url": "https://daily-tube.com/s/a1b2c3d4", "is_shared": true, "reused": false },
"request_id": "5f0c…"
}
Errors
Every error returns the same body — code is the stable machine string, message is human:
{
"error": { "code": "summary.not_found", "message": "Summary not found" },
"request_id": "5f0c…"
}
{
"error": { "code": "summary.not_found", "message": "Summary not found" },
"request_id": "5f0c…"
}
| Code | Status | When |
|---|---|---|
summary.not_found | 404 | Foreign or unknown briefing — uniform across all id kinds |
summary.not_completed / not_saveable / not_shared | 400 | Action preconditions |
summary.already_sent / delivery_in_progress | 409 | send-early on a delivered / mid-flight briefing |
summary.not_automatic / not_ready | 422 | send-early on a non-automatic or still-generating briefing |
summary.dispatch_failed | 502 | The generation workflow failed to dispatch |
validation.invalid_input | 400 | Unknown action; malformed filters (see Errors) |
Every keyed endpoint on this page shares the per-key budget — Rate limits.