Creators

Create a campaign

Creates a campaign from a spec in the campaign sheet's vocabulary: cadence and payout window, video target, an optional brief, and the payouts (fixed salary, base payouts per video, CPM tiers, flat bonuses, a performance cap, the eligibility window). The organization's default currency and an anchor-dependent start date fill in when omitted. Validate first with POST /campaigns/validate to read the sentences describing the rules; pass its previewHash to guarantee the campaign written is the one reviewed.

post/campaigns

Request

  • Base URL: https://viral.app/api/v1
  • URL: https://viral.app/api/v1/campaigns
  • Auth: API key in header x-api-key

Request body

namestring required

Campaign name

descriptionstring

Campaign description

currency'USD' | 'EUR' | 'CHF' | 'GBP' | 'CNY' | 'JPY' | 'KRW' | 'THB' | 'IDR' | 'VND' | 'RUB'

Payout currency. Defaults to the organization's default currency.

videoTargetinteger required

Videos a creator should publish per cycle, in videoTargetUnit: every post on every platform ("videos", the default), each video once whatever platforms it is posted to ("creatives"), or the sum of videoTargetPlatforms ("per_platform", where the platforms' sum replaces the value sent).

videoTargetUnit'videos' | 'creatives' | 'per_platform'

What the video target counts: "videos" (every post on every platform, a cross-post on three platforms counts 3), "creatives" (each video once, its copies on other platforms included) or "per_platform" (a count per platform in videoTargetPlatforms; videoTarget is their sum). Defaults to "videos".

videoTargetPlatformsobject nullable

Videos per platform per cycle, for videoTargetUnit "per_platform" (e.g. {"tiktok": 30, "instagram": 30}). Posts on other platforms do not count toward the target.

briefIdstring nullable

Campaign brief ID with orgbrief_ prefix

previewHashstring

The previewHash a dry-run returned for this exact spec. When present, a spec whose hash differs is refused (CAMPAIGN_PREVIEW_MISMATCH), so what was reviewed is what gets created.

Example request

{
  "name": "Creators Spain",
  "description": "Spanish-speaking creators, TikTok first",
  "currency": "EUR",
  "schedule": {
    "interval": "month",
    "intervalCount": 1,
    "anchor": "rolling",
    "startsOn": "2026-10-01"
  },
  "videoTarget": 30,
  "payouts": {
    "basePayouts": [
      {
        "perVideo": 10,
        "minViews": 1000,
        "platforms": [
          "tiktok",
          "instagram"
        ]
      }
    ],
    "cpmPayouts": [
      {
        "scope": "per_video",
        "tiers": [
          {
            "from": 0,
            "to": 100000,
            "rate": 1
          },
          {
            "from": 100000,
            "rate": 1.5
          }
        ]
      }
    ],
    "performanceCap": {
      "amount": 1000
    }
  }
}

Response

OK

idstring required

Campaign ID with orgcamp_ prefix

namestring required
descriptionstring nullable required
currency'USD' | 'EUR' | 'CHF' | 'GBP' | 'CNY' | 'JPY' | 'KRW' | 'THB' | 'IDR' | 'VND' | 'RUB' required
interval'day' | 'week' | 'month' required
intervalCountinteger required
billingAnchor'rolling' | 'fixed' required
timeZonestring required

IANA time zone the campaign's periods and dates follow (a period ends at midnight in this zone). Views are still counted per UTC day.

videoTargetinteger required

Videos per cycle in videoTargetUnit (with "per_platform": the sum of videoTargetPlatforms).

videoTargetUnit'videos' | 'creatives' | 'per_platform' required

What the video target counts: "videos" (every post on every platform, a cross-post on three platforms counts 3), "creatives" (each video once, its copies on other platforms included) or "per_platform" (a count per platform in videoTargetPlatforms; videoTarget is their sum).

videoTargetPlatformsobject nullable required

Videos per platform per cycle, with videoTargetUnit "per_platform"; null otherwise (create and update read a null sent back as none).

validFromstring date-time required
validUntilstring date-time nullable required
status'upcoming' | 'active' | 'closing' | 'ended' required

Campaign lifecycle status derived from validity window. upcoming: validFrom in the future; active: started with no end date; closing: started and has an end date in the present/future; ended: end date is in the past.

createdAtstring date-time required
updatedAtstring date-time required
orgIdstring required

Organization ID with org_ prefix

userIdstring required

User ID with user_ prefix

actorType'user' | 'system' | 'api_token' required
headlinestring required

One line: payouts and cadence, e.g. “€10.00 / video + CPM €1.00–€1.50 · monthly, rolling”.

hasPaidPeriodsboolean

A payout (not withdrawn) already priced this campaign's rules. Views conditions may still change, but thresholds are judged per video without memory: a raised one pays videos that were already paid again, a lowered one never pays earlier views of videos that newly qualify (updates return the views_conditions_changed_after_payouts warning). A changed total videos minimum, or a changed video target one follows, re-judges every period not paid yet (total_videos_changed_after_payouts).

conditionTagNamesobject

Names of the tags this campaign's payout conditions use, keyed by tag id. A tag that cannot be found in your organization is left out.

lockedFieldsstring[] required

Spec fields that can no longer change because the campaign has creator assignments or payout history. Sending their current value still passes.

Example response

{
  "id": "orgcamp_RwmraAffhMt9",
  "timeZone": "UTC",
  "videoTargetUnit": "videos",
  "status": "active",
  "spec": {
    "currency": "USD",
    "schedule": {
      "interval": "month",
      "intervalCount": 1,
      "anchor": "rolling",
      "startsOn": "2026-10-01",
      "timeZone": "UTC"
    },
    "videoTarget": 30,
    "videoTargetUnit": "videos",
    "briefId": "orgbrief_RwmraAffhMt9",
    "payouts": {
      "fixedSalary": {
        "id": "rule_A1b2C3d4E5f6"
      },
      "basePayouts": [
        {
          "id": "rule_A1b2C3d4E5f6",
          "perVideo": 10,
          "steps": [
            {
              "id": "rule_A1b2C3d4E5f6",
              "perVideo": 45
            }
          ],
          "minViews": 1000,
          "platforms": [
            "tiktok",
            "instagram"
          ],
          "conditions": {
            "match": "all",
            "items": [
              {
                "field": "contentType",
                "operator": "is",
                "values": [
                  "slideshow"
                ]
              },
              {
                "field": "views",
                "operator": "atLeast",
                "value": 10000
              }
            ]
          },
          "scope": "per_creative",
          "creatives": "once"
        }
      ],
      "cpmPayouts": [
        {
          "scope": "per_video",
          "creatives": "bestInWindow",
          "platforms": [
            "tiktok",
            "instagram"
          ],
          "conditions": {
            "match": "all",
            "items": [
              {
                "field": "contentType",
                "operator": "is",
                "values": [
                  "slideshow"
                ]
              },
              {
                "field": "views",
                "operator": "atLeast",
                "value": 10000
              }
            ]
          },
          "tiers": [
            {
              "id": "rule_A1b2C3d4E5f6",
              "to": 100000,
              "rate": 1
            }
          ]
        }
      ],
      "flatBonuses": [
        {
          "scope": "per_video",
          "creatives": "bestInWindow",
          "mode": "highest",
          "platforms": [
            "tiktok",
            "instagram"
          ],
          "conditions": {
            "match": "all",
            "items": [
              {
                "field": "contentType",
                "operator": "is",
                "values": [
                  "slideshow"
                ]
              },
              {
                "field": "views",
                "operator": "atLeast",
                "value": 10000
              }
            ]
          },
          "tiers": [
            {
              "id": "rule_A1b2C3d4E5f6",
              "to": 100000,
              "amount": 50
            }
          ]
        }
      ],
      "baseCap": {
        "id": "rule_A1b2C3d4E5f6"
      },
      "performanceCap": {
        "id": "rule_A1b2C3d4E5f6"
      },
      "eligibility": {
        "windowDays": 31,
        "publicationScope": "include_earlier"
      }
    }
  },
  "brief": {
    "id": "orgbrief_RwmraAffhMt9"
  },
  "warnings": [
    {
      "accounts": [
        {
          "platform": "facebook",
          "platformAccountId": "7123456789012345678"
        }
      ]
    }
  ]
}

Changes

No recorded changes to this operation across all 1 revision of this API.