Sources and campaigns
Two ways to keep producing over time: scan a feed when news breaks, or lay out a schedule by topic.
Which one to use
| News source | Campaign | |
|---|---|---|
| Triggered by | A new item on the feed | A slot time in the schedule |
| Content from | The scanned article itself | The topic list you supply |
| Runs until | You turn it off | The configured days run out, unless auto-renew is on |
News sources
GET /sources
GET /sources/{id}
POST /sources
PATCH /sources/{id}
DELETE /sources/{id}POST /sources needs an Idempotency-Key. A PATCH only changes fields that are present in the body; anything omitted is left alone.
| Field | Meaning |
|---|---|
feedUrl | RSS address or an article listing page |
enabled | Pause without deleting the configuration |
autoConnectionIds | Channels that receive videos from this source. In responses the field is named channelIds. |
maxItemsPerPoll | Cap on VIDEOS per scan — not on articles attempted |
maxItemsPerDay | Cap on videos per day |
scanMode, scanTimes, scanDays, scanTimezone | Scan schedule: fixed interval, or only at the listed times |
includeKeywords, excludeKeywords | Filter articles before rendering |
maxAgeHours | Skip articles older than this |
stopWhenCreditBelow | Stop scanning when the balance drops under this — a safety catch worth setting |
scheduleMode, scheduleDelayMinutes, immediateSpacingMinutes | Publish now or later, and the spacing between posts |
comment | Auto-posted first comment. Send null to disable; omit to keep the current setting. |
Responses carry nextScanAt, lastPolledAt and lastSuccessAt so you can tell a live source from a quiet one.
lastSuccessAt says nothing about whether videos were produced
A successful scan can yield zero videos: no new items, everything filtered out by keywords, or the source page changed shape so no image could be extracted. Read GET /sources/{id}/items to see what actually happened.
Scanned items
GET /sources/{id}/items?page=1&pageSize=25Each entry is a discovered article with status, attempts and videoId — the video built from it, matching GET /videos/{id}. Items with no video yet have videoId: null.
Campaigns
GET /campaigns?page=1&pageSize=25&status=ACTIVE
GET /campaigns/{id}
POST /campaigns → 201status accepts DRAFT, ACTIVE, PAUSED, COMPLETED or CANCELLED.
POST /campaigns creates a draft: no schedule laid out, nothing charged. Needs an idempotency header.
{
"name": "Personal finance — October",
"kind": "video-talk",
"connectionIds": ["4e1c…"],
"durationDays": 30,
"startAt": "2026-10-01T00:00:00.000Z",
"videosPerDay": 2,
"timezone": "Asia/Ho_Chi_Minh",
"timeWindows": ["08:00", "20:00"],
"topics": ["Compound interest", "Emergency fund"],
"autoRenew": false
}timeWindowsare times of day intimezone, not server time. The count should matchvideosPerDay.topicsare cycled through; the list wraps around when exhausted.autoRenewrestarts the campaign at the end of a cycle and keeps spending credits.
Campaign lifecycle
POST /campaigns/{id}/launch → 202
POST /campaigns/{id}/pause → 200
POST /campaigns/{id}/resume → 200
POST /campaigns/{id}/cancel → 200| Command | Effect | Reversible? |
|---|---|---|
| launch | Lays out slots for the whole run. From here each slot spends credits at its time. | No — but you can pause right after |
| pause | Stops rendering and publishing | Yes |
| resume | Starts again, and spends again | Yes |
| cancel | Stops for good. Slots that have not come due will never run. | NO — cannot be reopened |
All four re-read the campaign after the change, so you get the new state, not the old one. None require an idempotency header: calling twice equals calling once.
To stop temporarily, pause — do not cancel
cancel cannot be undone.
Slots
GET /campaigns/{id}/slots?page=1&pageSize=25A slot is one scheduled render-and-publish: scheduledAt, topic, status, attempts, and videoId once rendered. Failed slots carry errorCode and errorDetail.
A missed slot is not posted late. The schedule simply moves to the next mark — dumping several posts at once on a real channel is the fastest way to get throttled by the platform.
For cost, read creditSpent (cumulative) and unitCost (per video) on the detail endpoint. See also Campaigns in the web app.
Quick health check
renderedVideoCount and failedSlotCount on the detail endpoint tell you whether a campaign is running smoothly — watch those two rather than counting slots by hand.
