Skip to content
UGC API

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

FieldTypeDescription
accountsstring[], requiredAccount IDs to post to.
postsobject[], required1–1,000 items. Each has format, media, caption, and optional options and external_id, as in create a post. No accounts or scheduled_at.
spreadbooleanDefault true: each post goes to one account, taking turns. false: every post goes to all accounts.
cadenceobject{ "per_day": 1 } (1–6 posts per account per day) or { "every_days": 2 } (one post every N days). Default 1 per day.
timesstring[]Local posting times, HH:MM, used in order for per_day. Default ["10:00", "15:00", "20:00"].
timezonestringIANA timezone for times and start_date, like America/New_York. Default UTC.
start_datestringFirst day, YYYY-MM-DD. Default today. Times already past are skipped.
bash
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

  1. 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.
  2. Cadence per account. Each account posts on its own cadence at times. Days needed = posts / (accounts × per_day), rounded up: 100 posts × 5 accounts × 1 a day = 20 days.
  3. 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.
  4. Normal posts. Each assignment becomes a regular post with its own ID, results and webhooks, all tagged with the campaign's campaign_id.

Response

json
{
  "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.disconnected webhook. 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 posts so 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.

Related