curl https://viral.app/api/v1/briefs/orgbrief_AbCdEf123456 \
-H "x-api-key: $VIRAL_APP_API_KEY"
Briefs via the API
Write creator briefs in markdown or TipTap JSON, attach sample videos from your tracked accounts or the viral video library, and link briefs to campaigns from your own tools or an agent.
A brief is what creators read before they make content for a campaign: the instructions, and sample videos that show what you want. The briefs API covers the same briefs as the Briefs tab. You write the content as markdown or as the editor's TipTap JSON, and every read returns both, so a script, a CMS or an AI agent can work in whichever format it handles.
The API reference lists every field under Campaign Briefs, and API and automation covers keys and access. Campaigns themselves have their own guide: Campaigns via the API.
Before you start
- Base URL and auth: every route below lives under
https://viral.app/api/v1and takes thex-api-keyheader. Bodies and responses are JSON. - Who can do what: creating, editing, deleting and linking briefs needs a key of an owner, admin or member. Reads also work for a viewer's key. Writes land in the workspace's activity log under the key's owner.
- Ids carry their type:
orgbrief_briefs,orgcamp_campaigns,orgacc_tracked accounts,orgrsv_viral video library videos. - Names are unique per workspace. They are internal: creators never see a brief's name.
Read briefs
GET /api/v1/briefs lists briefs with a plain-text excerpt, their campaigns, the number of sample videos and notifiableCreatorCount. Filter by search (the name) or campaignIds, and page with page and perPage.
GET /api/v1/briefs/{id} returns the whole brief:
Write the content
Send the content as contentMarkdown or as content, never both. Markdown is the simpler choice:
## The assignment
Hi $creator_first_name, show how **Acme** gets you through exam week.
## Must include
- The planner screen in the first 3 seconds
- A link to [acme.com](https://acme.com)
- Supported:
##and###headings, paragraphs,**bold**,*italic*, links, images,-and1.lists. Code, quotes, rules and tables become plain paragraphs, and deeper headings become###. - Links must be absolute
https,httpormailtoURLs. Images need anhttpsURL. - Variables fill in per creator when they read the brief:
$creator_first_name,$creator_full_name,$creator_email,$creator_accounts,$brand_name,$campaign_video_target,$campaign_payout_cycleand$assignment_start_date. Any other$nameis refused as a typo, so creators never read a raw placeholder. Prices like$50are fine.
With content, send the editor's TipTap JSON: doc, paragraph, heading (levels 2 and 3), bulletList, orderedList, listItem, text, hardBreak, imageResize (images; TipTap's stock image is accepted and renamed) and variable nodes ({ "type": "variable", "attrs": { "id": "creator_first_name" } }), and the bold, italic, strike, underline and link marks. Headings of other levels become level 2 or 3. The easiest way to learn the shape is to read a brief you wrote in the dashboard.
Content the editor could not have produced is refused with 400 CONTENT_INVALID. data.issues lists every problem at once, each with a path and a message:
{
"code": "CONTENT_INVALID",
"status": 400,
"message": "The rich-text content is invalid.",
"data": {
"issues": [{ "path": "contentMarkdown", "message": "Unknown variable $creator_frist_name" }]
}
}
A brief also needs readable text: sample videos alone are not a brief.
Create a brief
POST /api/v1/briefs creates version 1 and returns its id and version:
curl -X POST https://viral.app/api/v1/briefs \
-H "x-api-key: $VIRAL_APP_API_KEY" \
-H "content-type: application/json" \
-d '{
"name": "Acme — exam week demo",
"contentMarkdown": "## The assignment\n\nHi $creator_first_name, show how Acme gets you through exam week.",
"campaignIds": ["orgcamp_RwmraAffhMt9"],
"notify": false
}'
campaignIds links campaigns right away (see Link campaigns). With notify: true and linked campaigns, their creators are told about the new brief in the app and by email.
Edit a brief
PUT /api/v1/briefs/{id} changes only what you send: name, the content, sampleVideos (the whole list) and campaignIds. Leave a field out to keep it, so renaming a brief never touches its samples or campaigns.
notify: falsesaves silently. The text changes in place and the version stays.notify: truepublishes the change: the version goes up, creators on the brief's campaigns are notified and have to read it again. Sent with nothing else, it re-publishes the brief as it is, for a brief you created silently.
notifiableCreatorCount on GET /briefs/{id} tells you beforehand whether a notify would reach anyone.
Sample videos
Sample videos show creators what you want. Add them to the end of the list with POST /api/v1/briefs/{id}/samples:
curl -X POST https://viral.app/api/v1/briefs/orgbrief_AbCdEf123456/samples \
-H "x-api-key: $VIRAL_APP_API_KEY" \
-H "content-type: application/json" \
-d '{
"videos": [
{ "libraryVideoId": "orgrsv_AbCdEf123456", "label": "Money-glitch hook" },
{ "platform": "tiktok", "platformVideoId": "7412345678901234567" }
],
"notify": false
}'
- Your tracked videos go by
platformandplatformVideoId. For a video tracked through an account rather than on its own, add the account asorgAccountId(orplatformAccountId). - Viral video library videos go by
libraryVideoId, theorgrsv_id from the library endpoints. viral.app tracks each one as a reference-only sample: it counts against your tracked-videos limit, stays out of your analytics and payouts, and is untracked again when no brief uses it anymore. The Basic plan cannot track individual videos and gets402. labelis the short caption creators read above the video, after its number ("2. Money-glitch hook"). Up to 60 characters.- Videos the brief already lists are skipped and counted in
skippedCount. One unknown video refuses the whole call and nothing is added.
To reorder, relabel or remove samples, send the complete list back as sampleVideos on PUT /briefs/{id}: take sampleVideos from GET /briefs/{id}, change it, and send it. Order is the array order, and a video you leave out is removed. Sent on POST /briefs, the same list sets the first samples; library videos are added afterwards through /samples.
Link campaigns
A campaign holds one brief, and one brief can serve several campaigns. Two ways to link them:
- From the brief:
PUT /api/v1/briefs/{id}/campaignswith the full list ofcampaignIdsthe brief should be on. Campaigns left out are unlinked; a campaign that had another brief switches to this one. Withnotify: true, creators on the newly linked campaigns are told. - From the campaign:
briefIdonPOST /campaignsandPUT /campaigns/{id}, withnotifyCreatorsOfBriefChangeon the edit (see Campaigns via the API).
Campaigns that have ended cannot be linked (409 CAMPAIGN_ENDED). Unlinking never notifies anyone.
Delete a brief
DELETE /api/v1/briefs/{id} removes the brief, its campaign links and read receipts, and untracks the sample videos only this brief used. Nobody is notified.
Webhooks for briefs
Subscribe to brief.published (a brief reached its creators: created or updated with a notification, or linked to a campaign) and brief.read (a creator read a version for the first time) in the Webhooks tab. See Webhooks.
Jobs use the same format
Job postings take their rich text the same way: bodyMarkdown or body, and challengeBriefMarkdown or challengeBrief, on POST /jobs and PUT /jobs/{id}. Every job response carries both formats.