Campaign pairs (per-post rows)
One row per video pair — a creator's post, or the 1–3 cross-posted videos that share a single payout — for the given cycles. This is the layer underneath GET /campaign/summary and GET /campaign/tags: per-pair totalSpend and views sum to the campaign figures those endpoints report for the same cycles. Each row carries the creator, the pair's tag ids, its money (basePay + cpmBonus = totalSpend), and a videos array with the platform, post URL, posted-at, and engagement of every video in the pair.
Engagement comes from the latest snapshot inside the cycle window — the same rows the campaign totals count. A video with no in-cycle snapshot has snapshotDate: null and zeroed metrics, and is not counted by any campaign endpoint; countedVideos is how many of the pair's videos did count.
tagIds filters which pairs come back (pair tags carry the money); each returned pair still reports all of its own videos.
groupIds filters by the pair's creator instead — the creator groups from GET /groups. Filters compose as an AND (groupIds plus tagIds means both), while ids within one filter are ANY. Every row also carries creator.groupIds, so one unfiltered pass can be bucketed by group client-side.
Results are ordered by (updatedAt, pairId) ascending and paged with the opaque nextCursor. Pass updatedSince with the largest updatedAt you have already stored to pull only what changed since — updatedAt moves whenever the pair is re-paired, re-assigned, or its payout is recalculated (which is what a view refresh triggers).
AuthorizationBearer <token>Provide your API key as Authorization: Bearer <key>.
cycleIds*stringComma-separated cycle ids to include in the campaign.
tagIds?stringComma-separated tag ids. Returns only pairs carrying at least one of these tags. Get tag ids from GET /campaign/tags.
groupIds?stringComma-separated creator-group ids. Returns only pairs whose creator is in at least one of these groups. Get group ids from GET /groups. Pass the literal __ungrouped__ to include creators that are in no group at all; it may be combined with real group ids (the two populations are unioned). An id that is not one of your brand's groups is a 400, never a silently empty page.
creatorId?stringReturn only this creator's pairs.
updatedSince?stringISO-8601 timestamp. Returns only pairs whose updatedAt is strictly greater — the incremental-sync cursor.
date-timelimit?integerRows per page (1–500). Defaults to 100.
1 <= value <= 500100cursor?stringThe nextCursor from the previous response. Omit for the first page.
A page of pairs.
application/json- response
currency?stringpairs?array<>nextCursor?|Pass as cursor for the next page; null on the last page.
hasMore?booleancurl -X GET 'https://example.com/api/v0/campaign/pairs?cycleIds=cyc_abc%2Ccyc_def&groupIds=grp_abc%2Cgrp_def&updatedSince=2026-07-01T00%3A00%3A00Z'{ "currency": "string", "pairs": [ { "pairId": "string", "cycleId": "string", "creator": { "creatorId": "string", "name": "string", "handles": { "instagram": "string", "tiktok": "string", "youtube": "string", "facebook": "string" }, "groupIds": [ "string" ] }, "firstVideoAt": "2019-08-24T14:15:22Z", "videoCount": 0, "countedVideos": 0, "views": 0, "likes": 0, "comments": 0, "shares": 0, "saves": 0, "basePay": 0, "cpmBonus": 0, "totalSpend": 0, "excludedFromPayout": true, "tagIds": [ "string" ], "updatedAt": "2019-08-24T14:15:22Z", "videos": [ { "videoId": "string", "platform": "string", "handle": "string", "postUrl": "string", "postedAt": "2019-08-24T14:15:22Z", "snapshotDate": "2019-08-24", "views": 0, "likes": 0, "comments": 0, "shares": 0, "saves": 0 } ] } ], "nextCursor": "string", "hasMore": true}Campaign spend by tag GET
Per-tag breakdown of a campaign's spend and reach. For each of your brand's tags, returns the videos, views, spend (Base + CPM), and CPM attributable to that tag across the campaign's cycles. Spend is attributed by *pair* tag (pairs carry the payout). A pair with multiple tags counts in each tag's bucket, so per-tag totals can exceed the campaign total. Every brand tag is returned, zero-filled when it has no activity. Use the returned `tagId` values as `tagIds` on `GET /campaign/summary` and `GET /campaign/daily`. `groupIds` narrows the population the breakdown describes — "how did this squad's spend split across tags?". The catalog itself is unaffected: every brand tag still comes back, zero-filled where that group has no activity.
Webhooks
Get Discord or HTTP alerts when videos cross view targets, links go dead, creators stop posting or handles change, plus a daily performance report.