YouTube reviews that wait for the real data
Review every new video twice: a 48-hour delivery check, then a day-5 diagnosis once YouTube Analytics catches up. Causes are tagged by confidence, fixes need your OK, and every run adds to a learnings doc.
What it does
- Waits out YouTube Analytics' 3-day lag instead of reading early zeros as failure.
- Judges each video against your channel's own baseline, not vanity targets.
- Tags every cause as confirmed, likely or speculative, and never invents CTR.
- Builds a learnings doc that turns repeated evidence into rules for future videos.
- Asks before changing any title, description, tag or thumbnail.
Before you start
- YouTube Data API and Analytics API connected as Custom APIs by the channel owner.
- A Type document for the learnings log (starter structure included).
See an example
Example output from a sample run. Unless the example says otherwise, company names and figures are sample data.
Example Pass B review (illustrative, not real data)
Verdict: below the channel's per-day rate (1.1 vs 1.9 views/day at day 5). Pass B.
Numbers: 6 views, 71% avg view %, 0 subscribers gained. Impressions and CTR: not available via API (Studio reach tab link included).
Causes
- CONFIRMED: no placement. 0 EXT_URL and 0 ADVERTISING views; organic search brought 2.
- LIKELY: low topic demand for the title's query.
- SPECULATIVE: thumbnail. Can't judge without impressions.
Actions (a) This video: add the docs link and two chapter markers to the description (needs approval). (b) Upcoming: lead the next title with the problem, not the feature name. (c) Distribution: embed on the matching docs page; share in the onboarding email. (d) Pipeline: add a "placement plan" field to the publishing checklist (needs approval).
Asking approval for: (a) description edit, (d) checklist change.
How to use YouTube reviews that wait for the real data
- Select Use automation to open a new conversation with the automation attached.
- Choose a space and describe any changes you want before sending the message.
- Work with the agent to connect your integrations and review the automation before turning it on.
Browse the automation instructions
---
name: "YouTube Post-Publish Performance Review"
description: "Review every new video twice, a 48-hour delivery check and a day-5 diagnosis once YouTube Analytics catches up, against the channel's own baseline. Infer causes honestly, recommend changes, and record learnings so each run starts smarter. Never changes a video without approval."
connection_needs:
- "YouTube Data API (Custom API, base URL https://www.googleapis.com) authorized by the channel owner: read playlist items, videos, channels. Writes only after approval."
- "YouTube Analytics API (Custom API, base URL https://youtubeanalytics.googleapis.com) authorized by the channel owner: read /v2/reports with ids=channel==MINE."
- "Type: read and edit the learnings document (apply_markdown_artifact_edit) and post the review in the Space."
inputs:
- "destination_space: the Space where the automation runs and posts its reviews"
- "channel_id: the YouTube channel ID (UC…)"
- "channel_handle: the channel handle, for example @yourchannel"
- "uploads_playlist_id: the channel's uploads playlist ID (the channel ID with UC replaced by UU)"
- "youtube_data_connection: Custom API connection to https://www.googleapis.com authorized by the channel owner"
- "youtube_analytics_connection: Custom API connection to https://youtubeanalytics.googleapis.com authorized by the channel owner"
- "learnings_document: Type document that holds the baseline, rules, hypotheses and run log (starter structure below)"
- "publish_prep_automation: optional name of the automation or process that schedules and releases videos"
- "release_ledger_hint: optional rule for finding go-public times of videos released by flipping private to public"
- "ignored_video_ids: optional test uploads to ignore"
- "thumbnail_skill: optional skill that generates thumbnails; otherwise thumbnail changes are described in words"
- "company_site: your website domain, used to read external referrers"
- "schedule_timezone: time zone for the daily run (source default: America/Denver, 09:00)"
---
# YouTube Post-Publish Performance Review
## Purpose
Most teams look at a new video once, two days after publishing, and change the thumbnail because that is what they know how to change. YouTube Analytics lags about three days, so the data that explains performance does not exist yet at 48 hours. This automation runs daily and gives every new public video two passes:
- **Pass A (~48h):** a delivery check from the Data API only (views, likes, comments, duration): did it publish correctly, is the thumbnail right, is it findable. No causal analysis.
- **Pass B (~day 5):** the diagnosis from YouTube Analytics: traffic sources, retention, average view %, subscriber attribution, external referrers.
It judges every video against the channel's own trailing baseline, ranks causes by explanatory power with CONFIRMED / LIKELY / SPECULATIVE tags, recommends changes to the video, upcoming videos, distribution and the publishing process, and asks for approval before anything changes. Every run updates a learnings document (baseline, confirmed rules, open hypotheses, run log), which is the automation's memory.
## Setup
Settle every binding before creating the automation. None inherits a value from the original workspace.
| Binding | Meaning and how to supply it |
| --- | --- |
| `{{input:destination_space}}` | Space where the automation runs; reviews and approval requests are posted there. |
| `{{input:channel_id}}`, `{{input:channel_handle}}`, `{{input:uploads_playlist_id}}` | From YouTube Studio or `GET /youtube/v3/channels?part=contentDetails&mine=true` (`contentDetails.relatedPlaylists.uploads`). |
| `{{input:youtube_data_connection}}`, `{{input:youtube_analytics_connection}}` | Two Custom API connections, both authorized as the channel owner (see below). Save them in `integrationAccounts`; never put connection IDs in instructions. |
| `{{input:learnings_document}}` | A Type document. Create it from the starter structure below before the first run. |
| `{{input:publish_prep_automation}}` | Optional: the automation or documented process that schedules and releases videos, so pipeline proposals name it. |
| `{{input:release_ledger_hint}}` | Optional: how to find the go-public time when videos are released by flipping private → public (for example "search this Space for messages `RELEASED <date> <video ID>` posted by the release automation"). Without it, scheduled `publishAt` values and the run log are used, and unknown times are labeled estimates. |
| `{{input:ignored_video_ids}}` | Optional list of test uploads to skip. |
| `{{input:thumbnail_skill}}` | Optional thumbnail skill. The workflow does not depend on it. |
| `{{input:company_site}}` | Domain whose referrals count as on-site embeds. |
| `{{input:schedule_timezone}}` | Run time zone. The source ran every day at 09:00 America/Denver. |
### YouTube connections (exact requirements)
Both calls must authenticate as the **channel owner** with Google OAuth. A plain API key is not enough: Analytics reports use `ids=channel==MINE`, which requires the owner's authorization.
| Connection | Base URL | Endpoints used | OAuth scopes |
| --- | --- | --- | --- |
| YouTube Data API | `https://www.googleapis.com` | `GET /youtube/v3/playlistItems`, `GET /youtube/v3/videos`, `GET /youtube/v3/channels` | `https://www.googleapis.com/auth/youtube.readonly` for the review; add `https://www.googleapis.com/auth/youtube` only if approved metadata or thumbnail changes should be made through this connection. |
| YouTube Analytics API | `https://youtubeanalytics.googleapis.com` | `GET /v2/reports` | `https://www.googleapis.com/auth/yt-analytics.readonly` |
Create an OAuth client in the Google Cloud project that has both APIs enabled, have the channel owner grant consent, and enter the client credentials and token URL `https://oauth2.googleapis.com/token` in the connection form's OAuth option (use a catalog provider with its own OAuth setup if your workspace offers one for Google or YouTube). Make the connections Personal to the channel owner and create the automation as that person. Before enabling, verify both with `api_get`:
- Data API: `GET /youtube/v3/channels?part=statistics&id={{input:channel_id}}` returns the channel.
- Analytics: `GET /v2/reports?ids=channel%3D%3DMINE&startDate=<30 days ago>&endDate=<today>&metrics=views&dimensions=day` returns rows.
### Starter structure for the learnings document
```markdown
# YouTube performance learnings — <channel name>
Channel: <handle> · `<channel ID>`
This is the memory for the post-publish review automation. It runs after each video publishes, records what happened, and reads this file first so each run starts from everything learned so far. Hypotheses get promoted to rules only when evidence repeats.
## How to read this file
- **Baseline** is the bar a new video is judged against. Update it as the channel grows.
- **Rules** are confirmed. The publishing process must follow them.
- **Open hypotheses** are unconfirmed guesses with a stated test. Never treat as fact.
- **Run log** is one row per reviewed video. Append, never rewrite.
## Baseline (recomputed <date>)
| Stat | Value |
| --- | --- |
| Median lifetime views (excluding outliers and videos under 7 days) | |
| n | |
| Subscribers | |
| Channel lifetime views | |
| Channel avg view percentage | |
| Typical first-48h views | |
Outliers excluded from the median (video ID, views, why):
## Rules (confirmed)
None yet. Promote a hypothesis only at 3+ agreeing videos or one unambiguous mechanism, and record n= inline.
## Open hypotheses (unconfirmed — do not state as fact)
- **H1: organic discovery is a tiny share of views.** Test: lifetime traffic mix by insightTrafficSourceType across all videos; YT_SEARCH + RELATED_VIDEO share vs ADVERTISING and EXT_URL.
- **H2: low views reflect too few starts, not poor retention.** Test: avg view % of low-view videos vs the channel average.
## Instrumentation status
| Capability | Status |
| --- | --- |
| Data API via `api_get` | |
| Analytics API via `api_get` | |
| Last date with Analytics data at the latest run | |
## Run log
| Date | Video | Age | Views | Cohort median | Verdict | Action taken |
| --- | --- | --- | --- | --- | --- | --- |
```
**Create the automation** with `create_task`: the instructions under Reference implementation, `scheduleConfig: { mode: "specific_time", time: "09:00", days: all seven, timezone: {{input:schedule_timezone}} }`, and `integrationAccounts` for both YouTube connections. It is agent-only (`agentModeReason`: inferring causes from sparse metrics, ranking them honestly and writing recommendations is judgment; the API reads are incidental). Create it paused and enable it after the connection checks pass.
## Triggers and execution
Instruction (agent-only) automation, scheduled every day at 09:00 in `{{input:schedule_timezone}}`. No model or run settings are configured in the source (agent defaults). No code stage. A run with nothing due posts nothing.
## Workflow
1. **Load the learnings document first.** Its rules win over new conclusions unless new evidence is strong enough; then propose the edit explicitly.
2. **Find videos due.** List the uploads playlist and read video status, statistics and details. Pass A: public, went public 40–72h ago, no Pass A row. Pass B: public, went public 4.5–7 days ago, no Pass B row. Use `publishAt` for scheduled videos (not `snippet.publishedAt`, which is upload time), the release ledger for private → public releases, and label unknown go-public times as estimates. Skip ignored IDs.
3. **Pull metrics.** Pass A: Data API only. Pass B: Analytics totals, traffic mix and external referrers per video. Always check the last date with Analytics data first; a zero before that date is a reporting artifact, not a finding. Never report impressions or CTR unless a human read them from Studio.
4. **Baseline.** Median lifetime views excluding listed outliers; compare young videos to same-age figures from the run log or the per-day rate, and say which.
5. **Causes.** Rank: distribution/placement, topic demand, packaging (needs real impressions), the video itself (check avg view % first). Tag each CONFIRMED / LIKELY / SPECULATIVE.
6. **Recommendations** in four groups: (a) this video, (b) upcoming videos, (c) distribution, (d) pipeline. (a) and (d) need explicit approval.
7. **Post** the review in the Space: verdict first, pass letter, numbers with unavailable metrics named, tagged causes, grouped actions, and the approval list.
8. **Update the learnings document** every run: append the run-log row, update the baseline, promote rules only at 3+ agreeing videos or one unambiguous mechanism, and add or retire hypotheses with stated tests.
## State and failure behavior
- The learnings document is the only state: the run log records which passes are done, so neither pass repeats.
- A quiet run (nothing due) posts nothing; the document may still record the quiet run.
- If Analytics has not caught up, Pass B is deferred and the zero is not reported as performance.
- A connection error such as HTTP 400 "Direct API integration is not available to this user" stops the run with a report that the owner must connect it; no workaround, no token minting, no sandbox credential files. Transient HTTP 000 / network errors are retried a few times.
- No write happens without an explicit "yes" in the thread; deletes, privacy and `publishAt` changes are never made; a write is confirmed by a delayed read-back.
## Integration data and capabilities
| Integration | Data and capabilities used | Relevant semantics |
| --- | --- | --- |
| YouTube Data API (Custom API) | `playlistItems` (`contentDetails`, `snippet`), `videos` (`snippet`, `status.privacyStatus`, `status.publishAt`, `statistics.viewCount/likeCount/commentCount`, `contentDetails.duration`, `fileDetails` aspect ratio), `channels?part=statistics` (subscriberCount). | `snippet.publishedAt` is upload time. Bulk `videos` calls with `snippet` truncate; use `fields=`. `commentThreads` needs a comments-capable scope. |
| YouTube Analytics API (Custom API) | `/v2/reports?ids=channel==MINE` with metrics `views, estimatedMinutesWatched, averageViewPercentage, averageViewDuration, subscribersGained, likes, shares`; dimensions `day`, `insightTrafficSourceType`, `insightTrafficSourceDetail`, `subscribedStatus`; filters `video==<ID>`, `insightTrafficSourceType==EXT_URL` or `YT_SEARCH`. | ~3-day reporting lag. `dimensions=video` with a traffic-source filter is unsupported. `insightTrafficSourceDetail` on YT_SEARCH is empty at low volume. SUBSCRIBER is the Browse bucket. No impressions or CTR. |
| Type | `read_workspace_artifact` and `apply_markdown_artifact_edit` on the learnings document; the run's reply in the Space. | The document is the memory; keep it free of unsupported claims. |
## Reference implementation
### Automation instructions
Original provider assumptions: two Custom API connections to the YouTube Data and Analytics APIs authorized by the channel owner, and a Type learnings document. **Reuse when these assumptions hold; adapt when they do not, preserving the behavior above.**
```text
GOAL: After a video on the {{input:channel_handle}} YouTube channel goes public, analyse how it performed, infer WHY, and recommend concrete improvements to that video, to videos not yet published, and to distribution. Record durable learnings so every future run starts smarter. NEVER change a published video without explicit human approval.
Channel: {{input:channel_id}} ({{input:channel_handle}}). Uploads playlist: {{input:uploads_playlist_id}}.
Connections: `YouTube Data API` for Data API reads, and `YouTube Analytics API` for Analytics reports, both authorized by the channel owner. Read each connection's Custom API usage guidance (returned by search_integrations) before the first call to it, as the guidance requires.
Learnings artifact: `{{input:learnings_document}}`.
Publishing process (optional): {{input:publish_prep_automation}}.
=== TWO PASSES, AND WHY ===
YouTube Analytics has a ~3-day reporting lag (measured in the source workspace: day-dimension data ended three days before the run date, and videos published inside that window all returned views=0 while the Data API showed real views). A single 48h review cannot see the data that explains performance. So:
- **PASS A at ~48h** — Data API only (viewCount, likeCount, commentCount). A sanity check: did it publish correctly, is the thumbnail right, is it findable. NO causal analysis, because the diagnostic metrics do not exist yet.
- **PASS B at ~day 5** — Analytics has caught up. This is the real diagnosis: traffic sources, retention, avg view %, subscriber attribution.
A video gets BOTH passes. Track which passes are done in the artifact run log so neither repeats.
=== STEP 0 — Load prior learnings FIRST ===
Read artifact `{{input:learnings_document}}` before looking at any metrics. It holds the baseline, confirmed rules, open hypotheses, instrumentation caveats, and run log. If a rule contradicts a conclusion you are about to draw, the rule wins unless you have new evidence strong enough to change it — and if you do, say so explicitly and propose the edit rather than silently overriding.
=== STEP 1 — Find videos due ===
GET /youtube/v3/playlistItems?part=contentDetails,snippet&playlistId={{input:uploads_playlist_id}}&maxResults=50
GET /youtube/v3/videos?part=snippet,status,statistics,contentDetails&id=<ids>
DUE FOR PASS A: privacyStatus=="public", went public 40-72h ago, no Pass A row in the run log.
DUE FOR PASS B: privacyStatus=="public", went public 4.5-7 days ago, no Pass B row in the run log.
`snippet.publishedAt` is the UPLOAD time for videos uploaded private and scheduled later — NOT the go-public time. For a scheduled video the `publishAt` value is the go-public time. If videos are released by flipping private → public, find the go-public date as follows: {{input:release_ledger_hint}}. Also use the run log. If you cannot determine it, say so and label your timestamp an estimate.
IGNORE these video IDs (test uploads and probes): {{input:ignored_video_ids}}.
If nothing is due, post nothing and produce no output. A quiet run is a normal run.
=== STEP 2 — Pull metrics ===
Data API calls go through `api_get` on the `YouTube Data API` connection. Analytics calls go through `api_get` on the `YouTube Analytics API` connection (`/v2/reports` with ids=channel==MINE returns data, including per-video insightTrafficSourceType breakdowns). Do not mint OAuth tokens or use any sandbox credential file for this task; the two connections are its only auth path. If a connection call returns HTTP 400 "Direct API integration is not available to this user" or the connection is not connected for this run's user, stop and report that the connection must be connected for this automation's owner; do not work around it. Retry transient HTTP 000 / network errors a few times.
PASS A — Data API only: statistics.viewCount, likeCount, commentCount, contentDetails.duration.
PASS B — Analytics via the `YouTube Analytics API` connection, path `/v2/reports?ids=channel%3D%3DMINE`:
- Video totals: `&startDate=<pub>&endDate=<today>&metrics=views,estimatedMinutesWatched,averageViewPercentage,averageViewDuration,subscribersGained,likes,shares&filters=video%3D%3D<ID>`
- Traffic mix: `&metrics=views&dimensions=insightTrafficSourceType&filters=video%3D%3D<ID>&sort=-views`
- External referrers: `&metrics=views&dimensions=insightTrafficSourceDetail&filters=insightTrafficSourceType%3D%3DEXT_URL&sort=-views&maxResults=25`
KNOWN API SHAPES (verified in the source workspace, do not rediscover):
- `dimensions=video` COMBINED WITH an `insightTrafficSourceType` filter returns "query is not supported". Query one video at a time using `filters=video==<ID>`.
- `insightTrafficSourceDetail` on YT_SEARCH returns EMPTY at low traffic volume (it worked at ~100 search views with `filters=video==ID;insightTrafficSourceType==YT_SEARCH`). Not a bug.
- **ALWAYS run `&metrics=views&dimensions=day` for a recent window first and note the last date with data.** If that date is before the video's publish date, Analytics has NOT caught up: a zero is a reporting artifact, NOT a performance signal. Say so and defer, rather than reporting a zero as a finding.
IMPRESSIONS AND CTR ARE NOT AVAILABLE VIA ANY API — verified against Google's metrics docs. Studio UI only. NEVER report a CTR you did not get from a human, and never estimate one. If CTR matters, ask for it from:
https://studio.youtube.com/video/<VIDEO_ID>/analytics/tab-reach_viewers/period-default
=== STEP 3 — Cohort baseline ===
Judge against the channel's own trailing performance, never an absolute target.
- Pull statistics for all public videos. Compute median lifetime views, excluding the externally-promoted outliers listed in the learnings artifact's Baseline section (they can dominate a small channel's totals).
- Do NOT compare a young video's total against older videos' lifetime totals. Compare against the same-age figure from the run log, or against the trailing median's per-day rate, and SAY which you used.
- Recompute each run; update the artifact Baseline when it moves.
The current baseline lives in the learnings artifact. On a young channel, a mid-single-digit 48h view count can be normal and is NOT by itself evidence that packaging failed; judge it against the baseline.
=== STEP 4 — Infer causes honestly ===
The failure mode to avoid is blaming the thumbnail because the thumbnail is what we know how to change. The confirmed Rules in the artifact constrain this hard. Until the artifact has confirmed rules on these points, test these two starting hypotheses on every Pass B and record the evidence:
- **Organic discovery may be a tiny share of views** (YT_SEARCH + RELATED_VIDEO, compared with ADVERTISING and EXT_URL). If confirmed, a new video with no placement and no ad spend has no distribution path, and that becomes the default explanation for low views.
- **Retention may not be the problem.** If low-view videos have fine-to-excellent avg view %, never recommend re-editing for retention on the basis of low views.
Rank causes by explanatory power: 1) distribution/placement, 2) topic demand, 3) packaging (only judgeable with real impressions, which requires a human Studio read), 4) the video itself (check avg view % before ever suggesting this).
Tag every cause CONFIRMED / LIKELY / SPECULATIVE. Never dress a guess as a finding. If the honest answer is that the data cannot distinguish the causes, say exactly that.
=== STEP 5 — Recommendations, split by target ===
(a) THIS VIDEO — only if justified. Description/tag additions are low-risk. A thumbnail swap needs a concrete visual defect, not a low view count; note that swapping resets the CTR baseline so before/after is no longer comparable. A title rewrite needs a real reason, never view count alone inside 48h. When a thumbnail-generation skill is configured ({{input:thumbnail_skill}}), use it for proposed thumbnails and keep looks varied between videos; otherwise describe the proposed thumbnail change in words.
(b) UPCOMING VIDEOS — check for scheduled/private videos; be concrete about topic, title framing, thumbnail look, description structure.
(c) DISTRIBUTION — usually the actual lever. Use the EXT_URL referrer breakdown: if {{input:company_site}} drives most external views, prioritise on-site embeds and placement. Name specific destinations: the sourcing X post, LinkedIn, docs pages, onboarding email, sales follow-ups, relevant Reddit threads. If a video has no placement plan, say that is the finding.
(d) PIPELINE — propose exact edits to the publishing process ({{input:publish_prep_automation}}) or to a skill, and ask before making them.
=== STEP 6 — Post the review ===
Post the review as this run's reply in the Space. Lead with the verdict. Keep it tight.
- One-line cohort-relative verdict, and which pass this is (A or B).
- The numbers; state explicitly which metrics were unavailable and why.
- Causes tagged CONFIRMED / LIKELY / SPECULATIVE.
- Actions grouped (a)-(d), each specific enough to act on.
- An explicit short list of what you are asking approval for. Nothing in (a) or (d) happens without a yes.
For Pass A, say plainly that it is a delivery check and that causal diagnosis comes at day 5. Do not pad Pass A with speculation to make it feel substantial.
=== STEP 7 — Update the learnings artifact ===
Always, even on an inconclusive run. Use `apply_markdown_artifact_edit` on `{{input:learnings_document}}`:
- Append a run log row, noting whether it was Pass A or Pass B.
- Update Baseline if the trailing median moved.
- Promote a hypothesis to a Rule ONLY at 3+ agreeing videos, or one unambiguous mechanism. Record n= inline.
- Add new hypotheses with an explicit stated test. Amend or remove hypotheses the data contradicts; do not leave stale guesses.
This file is the automation's memory. Polluting it with confident nonsense poisons every later run.
=== GUARDRAILS ===
- NEVER modify a published video's title, description, tags, or thumbnail without explicit human approval in the thread. Analysis is automatic; writes are not.
- `videos.delete` is off limits. Never delete anything.
- Never change privacyStatus or publishAt.
- Never print an access token, refresh token, OAuth client ID, or client secret into a thread message, instructions, or scratchpad.
- Never invent a metric, especially impressions or CTR. If it was not measured, say so.
- Never report an Analytics zero as a performance finding without first confirming via `dimensions=day` that Analytics covers the video's date range.
- Do not repeat an unactioned recommendation in full each run; reference it in one line.
- Read-back lag is real: HTTP 200 echoing your values is not proof a write landed. If a read-back disagrees, wait 30-60s and re-read.
- On a quiet run with nothing due, post nothing.
=== OPERATIONAL NOTES ===
- `api_get` works directly on both connections; no OAuth file is needed.
- The Analytics SUBSCRIBER traffic source is the Browse bucket and is mostly non-subscribers; confirm with `dimensions=subscribedStatus`.
- Per-source averageViewPercentage can read 0 when the total is nonzero; treat it as an artifact.
- `/youtube/v3/channels?part=statistics&id={{input:channel_id}}` gives the authoritative subscriberCount.
- For Pass A findability, search the exact title in quotes.
- Shorts check: fileDetails aspect ratio, or youtube.com/shorts/<ID> returns 200 (Short) vs 303 (long video).
- Bulk videos calls with snippet truncate; use `fields=` without snippet.
- commentThreads returns 403 (insufficient scope) unless the Data API connection was authorized with a comments-capable scope.
```
## Verification
**Examples derived from the source rules (not executed against live accounts):**
- A video that went public 50 hours ago with no Pass A row gets Pass A only, explicitly labeled a delivery check with no causal claims.
- A video at day 5 whose Analytics day series ends before its publish date: Pass B is deferred, and the zero views are not reported as performance.
- A low-view video with 70% average view percentage: no re-edit recommendation for retention.
- Someone asks for the CTR: the review links the Studio reach tab instead of estimating it.
- Nothing due: no post.
- An approved title change: made only after a "yes" in the thread, then confirmed by a delayed read-back.
**Proposed acceptance checks after installation:**
1. Run both connection checks from Setup; both return HTTP 200 with data.
2. Run once with a video aged 2–3 days; confirm a Pass A review and a run-log row marked Pass A.
3. Run again at day 5; confirm a Pass B review with a traffic mix and tagged causes, and no repeated Pass A.
4. Run on a day with nothing due; confirm nothing is posted.
5. Reply "no" to a requested change; confirm the video is unchanged.