Campaigns
A campaign takes N posts, M accounts and a cadence per account, and turns them into a schedule. Use it instead of computing times in a loop. 100 posts across 5 accounts at 1 per day runs for 20 days.
Updated
POST /v1/campaigns
| Field | Type | Description |
|---|---|---|
accounts | string[], required | Account IDs to post to. |
posts | object[], required | 1–1,000 items. Each has format, media, caption, and optional options and external_id, as in create a post. No accounts or scheduled_at. |
spread | boolean | Default true: each post goes to one account, taking turns. false: every post goes to all accounts. |
cadence | object | { "per_day": 1 } (1–6 posts per account per day) or { "every_days": 2 } (one post every N days). Default 1 per day. |
times | string[] | Local posting times, HH:MM, used in order for per_day. Default ["10:00", "15:00", "20:00"]. |
timezone | string | IANA timezone for times and start_date, like America/New_York. Default UTC. |
start_date | string | First day, YYYY-MM-DD. Default today. Times already past are skipped. |
curl https://ugcapi.dev/v1/campaigns \
-H "Authorization: Bearer ugc_live_..." \
-H "Content-Type: application/json" \
-d '{
"accounts": ["acc_tt_01", "acc_tt_02", "acc_tt_03", "acc_tt_04", "acc_tt_05"],
"cadence": { "per_day": 1 },
"times": ["10:00"],
"timezone": "America/New_York",
"start_date": "2026-11-01",
"posts": [
{ "format": "video", "media": ["https://cdn.example.com/h/001.mp4"], "caption": "Hook 1" },
{ "format": "video", "media": ["https://cdn.example.com/h/002.mp4"], "caption": "Hook 2" }
]
}'How the schedule is built
- Round-robin. Posts are assigned in order: post 1 to account 1, post 2 to account 2, wrapping back to account 1 after the last account.
- Cadence per account. Each account posts on its own
cadenceattimes. Days needed = posts / (accounts × per_day), rounded up: 100 posts × 5 accounts × 1 a day = 20 days. - Staggered minutes. Each account's times are shifted 7 minutes from the previous account's (10:00, 10:07, 10:14…), so the roster doesn't post at the same minute.
- Normal posts. Each assignment becomes a regular post with its own ID, results and webhooks, all tagged with the campaign's
campaign_id.
Response
{
"id": "cmp_xyz",
"posts": 100,
"post_ids": ["post_1", "post_2", "..."],
"first_at": "2026-11-01T14:00:00.000Z",
"last_at": "2026-11-20T14:28:00.000Z"
}Changing a running campaign
- List its posts with
GET /v1/posts?campaign_id=cmp_xyz. - Cancel any single post with
DELETE /v1/posts/:id; its credits come back. - If an account's login expires or is revoked, its posts wait (up to 7 days) and you get an
account.disconnectedwebhook. Other accounts keep posting. When the creator reconnects, waiting posts go out. After 7 days of waiting they fail and are refunded. - If you disconnect an account yourself (
DELETE /v1/accounts/:id), its scheduled posts are cancelled and refunded. - The scheduler never publishes the same post twice to one account.
Credits
A campaign uses one credit per result, the same as single posts; Story frames count individually. Credits for the whole campaign are reserved when you create it, so it fails with 402 insufficient_credits if you don't have enough. Failed or cancelled results are refunded. See pricing and credits.
Tips
- Run one campaign per platform so each gets its own cadence.
- Order
postsso similar content is spread out; round-robin keeps neighbors on different accounts. - Start low. See UGC posting cadence and limits.
FAQ
Can I add accounts to a running campaign?
Not to an existing one. Create a new campaign for the extra accounts and remaining posts.
Can a campaign mix videos and slideshows?
Yes. Each item in posts has its own format and media.
What timezone are the times in?
The timezone you pass (an IANA name like Europe/London). Daylight saving is handled, so 10:00 stays 10:00 local all year.