Core concepts
Campaigns, cycles, spend, pairs, tags and creator groups — how the Campaign API models your brand.
The Campaign API describes your brand with a few building blocks: cycles, spend, pairs, tags and creator groups. Read this page before you call the endpoints, so the numbers they return make sense.
Campaigns are sets of cycles
A payout cycle is a date range your brand pays creators for. The API has no separate "campaign" object: a campaign is whatever cycles you pass in cycleIds. Get ids from GET /cycles.
?cycleIds=cyc_sep,cyc_oct → "Q3 launch" campaignSpend = base pay + CPM bonus
Spend is what your brand pays creators: the base pay for each qualifying pair plus the view-based bonus — the same amounts creators see in their payouts. All money is in USD.
GET /campaign/summary can also track a budget: pass the client's prepayment and the response adds metrics.totalRemains (prepayment minus spend), budget.budgetUsedPct, and budget.atThreshold — true once usage reaches thresholdPct (default 70%).
Pairs (sets)
A pair — called a set in the app — is one video a creator posted to up to four platforms (Instagram, TikTok, YouTube, Facebook). Pay lives on the pair, not on the individual posts. GET /campaign/pairs returns one row per pair with its posts nested under videos.
Tags and creator groups
Two filters narrow every campaign endpoint:
| Filter | Scopes to | Get ids from |
|---|---|---|
tagIds | pairs carrying any of these tags | GET /campaign/tags |
groupIds | pairs whose creator is in any of these groups | GET /groups |
- Ids within one filter combine as ANY (OR).
- The two filters combine as AND.
- Pass
__ungrouped__ingroupIdsfor creators in no group.
Daily spend and filters
GET /campaign/daily returns spend: null for every day when you filter by
tag or group — per-day spend can't be split that way. Use GET /campaign/summary (filtered) for a group's total spend, or GET /campaign/tags for spend by tag.
Dates and timezones
Dates you send (from, to) are calendar days, YYYY-MM-DD. Daily figures are bucketed by your brand's timezone (set in SettingsRulesTimezone), so "a day" matches what your team sees in Bloss.
Pagination
GET /campaign/pairs is paginated:
limit— rows per page, 1–500 (default 100).cursor— pass the previous response'snextCursor; omit it for the first page.nextCursorisnullon the last page.updatedSince— only pairs changed since a timestamp. To keep a copy in sync, store the largestupdatedAtyou've seen and pass it next time.
let cursor: string | null = null;
do {
const url = new URL('https://www.bloss.app/api/v0/campaign/pairs');
url.searchParams.set('cycleIds', cycleIds.join(','));
if (cursor) url.searchParams.set('cursor', cursor);
const res = await fetch(url, {
headers: { Authorization: `Bearer ${process.env.BLOSS_API_KEY}` },
});
const page = await res.json();
await save(page.pairs);
cursor = page.nextCursor;
} while (cursor);Last reviewed Oct 12, 2026