---
title: "Update a campaign"
method: PUT
path: "/campaigns/{id}"
tags: ["Creators"]
---

# Update a campaign

`PUT /campaigns/{id}`

## Request

- Base URL: `https://viral.app/api/v1`
- URL: `https://viral.app/api/v1/campaigns/{id}`
- Auth: API key in header `x-api-key`

Updates a campaign from a partial spec: name, description, videoTarget, briefId and the schedule keys replace only what is present, payouts replaces the whole block. The payout rules are re-derived from the merged spec whenever the payouts sent differ from the current ones, or videoTarget or currency change (a rename, or the current payouts sent back unchanged, leaves them untouched) and apply to all unpaid billing windows immediately; issued payouts keep their stored snapshots. currency and the schedule are editable only while the campaign has no creator assignments or payout history (CAMPAIGN_FIELD_LOCKED otherwise; echoing the current value passes); the end date is managed by the end/reactivate endpoints. Dry-run with POST /campaigns/validate and an id to see which sentences would change.

## Path parameters

- `id` string, required: Campaign ID with orgcamp_ prefix

## Request body

- object: Request to update a campaign. Always editable: name, description, the video target (videoTarget, videoTargetUnit, videoTargetPlatforms), briefId and payouts (rule changes apply to every unpaid billing window immediately; the current payouts sent back unchanged are a no-op). A unit change freezes payouts that follow the target but can no longer at the number they stopped at (a warning says so). currency and the schedule keys are editable only while the campaign has no creator assignments or payout history; afterwards a changed value fails with CAMPAIGN_FIELD_LOCKED (echoing the current value passes). A changed schedule.startsOn must fall between the 1st of the previous month and two years ahead. The end date is managed by the end/reactivate endpoints.
  - `name` string: Campaign name
  - `description` string: Campaign description
  - `currency` 'USD' | 'EUR' | 'CHF' | 'GBP' | 'CNY' | 'JPY' | 'KRW' | 'THB' | 'IDR' | 'VND' | 'RUB': Currency used for campaign payouts
  - `schedule` object
    - `interval` 'day' | 'week' | 'month': Payout cycle unit. Monthly = month × 1, biweekly = week × 2, weekly = week × 1.
    - `intervalCount` integer: How many units make one payout cycle (1–12).
    - `anchor` 'rolling' | 'fixed': rolling: each creator's cycle starts the day they join, so creators are added and paid on their own dates. fixed: everyone shares the same calendar cycle and payouts go out in one batch per cycle; a creator added mid-cycle gets that cycle as a first period cut to the join day, a fixed salary in it paid pro rata. Required — it locks once the campaign is in use.
    - `startsOn` string, date: Campaign start (YYYY-MM-DD). Defaults to today for rolling campaigns and to the start of the current week or month for fixed weekly or monthly ones. New campaigns cannot start before the 1st of the previous month.
    - `timeZone` string: IANA time zone the periods and dates follow: a period ends at midnight there, a video belongs to the period it was published in there, and "today" is the zone's day. Views are still counted per UTC day. Defaults to "UTC" on create; editable until a payout depends on its periods or a period is canceled (`CAMPAIGN_FIELD_LOCKED`).
  - `videoTarget` integer: Expected number of videos per payment period
  - `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). Omitted: unchanged (sending videoTargetPlatforms alone switches to "per_platform"). Base payouts that stop following the target this way keep its current number as their own.
  - `videoTargetPlatforms` object, 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.
  - `briefId` string, nullable: Campaign brief ID with orgbrief_ prefix
  - `payouts` object: Replaces every payout of the campaign when present.
    - `fixedSalary` object: A guaranteed amount per cycle, regardless of videos.
      - `id` string: Stable id of the stored rule (read it from GET). Send it back to keep the rule, and any creator's custom rate on it, attached through edits; omit it for new entries.
      - `amount` number, required: Amount in major units of the campaign currency (10 = 10.00).
    - `basePayouts` object[]
      - `id` string: Stable id of the stored rule (read it from GET). Send it back to keep the rule, and any creator's custom rate on it, attached through edits; omit it for new entries.
      - `perVideo` number, required: Paid per published video (until the first step).
      - `steps` object[]: Rate steps: a video is paid the rate of the step its place in the period falls in. An update that omits them keeps them; [] removes them.
        - `id` string: Stable id of the stored rule (read it from GET). Send it back to keep the rule, and any creator's custom rate on it, attached through edits; omit it for new entries.
        - `startsAfter` union, required: Videos before the step: a count or "videoTarget". Rises and stays below the end.
          - integer
          - 'videoTarget'
        - `perVideo` number, required: Paid per video from this step on.
      - `limit` object, nullable: Omitted: the base payout stops at the video target. null: unlimited, every extra video is paid.
        - `videos` union: Videos paid per cycle: a count (above the target: an overshoot allowance), or "videoTarget".
          - integer
          - 'videoTarget'
        - `amount` number: Deprecated: payouts.baseCap. Only with one base payout; without videos only this cap limits it.
      - `minViews` integer: Only videos reaching this many total views by the end of the payout period they were published in earn the base payout. The same as a views condition with countWhen byPublicationPeriodEnd; send one or the other.
      - `platforms` string[]: Platforms this entry applies to. Omit for all platforms. Entries of the same family may share platforms: a video on a shared platform earns each entry that covers it, and the dry-run warns.
      - `conditions` object: Which videos take part in this payout: content type (video/slideshow), tags (custom, project or geography tag ids; anyOf, allOf, noneOf), hashtags (anyOf, allOf, noneOf), views (atLeast or below, per video), totalViews (atLeast or below, the creator's total views) and totalVideos (atLeast or below, the creator's number of videos). On a per-video payout a video must match to be paid; on an all-content payout only matching videos' views add up. Views conditions work per video: base payouts count a video in the period it reaches an atLeast threshold (countWhen reached, the default) or only by the end of its publication period (countWhen byPublicationPeriodEnd, the same as minViews, and the only variant of a base below); per-video CPM payouts and bonuses pay a video's earlier views once it reaches an atLeast threshold, and pay a below one only for periods the video ends under the count. totalViews judges each payout period on the creator's total views over the payout's platforms, whatever the other conditions say (countFrom periodVideos: views of the videos published in the period, counted to its end; eligibleVideos: views gained in the period by every video inside its eligibility window): a period that misses pays nothing through this payout, and its views are never paid later. totalVideos judges each payout period on every video the creator published in it, on every platform (a post on three platforms counts 3), whatever the other conditions and the payout's platforms say; its value is a count or "videoTarget" (the campaign's video target, or the creator's own with custom rates, never prorated, counted in the target's unit: under creatives each video once whatever platforms it is on, under per_platform every listed platform must reach its own count and other platforms do not count). A period that misses pays nothing through this payout; on a base payout an atLeast row may carry the missed period's videos instead (carry previousPeriod: paid in the next period if that one reaches its own minimum; untilPaid: every missed period waits for the first that does), priced as if paid in the period they were posted in (its rate steps, its end and its base payout cap, never the paying period's), only directly in a match all group. One atLeast and one below make a range. Periods follow the campaign's schedule.timeZone; views are counted per UTC day. The platform list always applies on top.
        - `match` 'all' | 'any': all: a video must meet every condition. any: one condition is enough.
        - `items` union[], required
          - union
            - object
              - …
            - object
              - …
            - object
              - …
            - object
              - …
            - object
              - …
            - object
              - …
      - `scope` 'per_video' | 'per_creative': per_video (the default): every video counts. per_creative: copies of one video posted to several of this payout's platforms (grouped as a creative) count as one creative, their views added up: it earns one base payout, and views conditions judge the creative's combined views (directly in a match all group, never next to a totalVideos row that carries). It counts in the first period at whose end its views meet a reached atLeast condition, otherwise only in the period its first copy was posted in. Needs two or more platforms; not with creatives once.
      - `creatives` 'once': once: only each creative's winner counts. When the creator posts the same video to several of this payout's platforms (copies grouped as a creative), the copy with the most views earns one base payout, in the first period any copy is counted; views conditions judge each copy on its own. Needs two or more platforms; not with scope per_creative. Omit to count every copy.
    - `cpmPayouts` object[]
      - `scope` 'per_video' | 'per_creative' | 'all_content': per_video: applied to every video with its own views. per_creative: copies of one video posted to several platforms (grouped as a creative) count as one video, their views added up; a video in no creative counts on its own. all_content: applied once per cycle to the creator's aggregated views.
      - `creatives` 'bestInPeriod' | 'bestInWindow': Pay only each creative's winner: when the creator posts the same video to several of this payout's platforms (copies grouped as a creative), only the copy with the most views earns it, judged in the payout period (bestInPeriod) or in each copy's eligibility window (bestInWindow). A creative is never paid twice. Needs two or more platforms; not with scope per_creative or a current_period_only eligibility; bonuses per video take bestInWindow only, and CPM payouts per video with an atLeast views condition too. Omit to pay every copy.
      - `platforms` string[]: Platforms this entry applies to. Omit for all platforms. Entries of the same family may share platforms: a video on a shared platform earns each entry that covers it, and the dry-run warns.
      - `conditions` object: Which videos take part in this payout: content type (video/slideshow), tags (custom, project or geography tag ids; anyOf, allOf, noneOf), hashtags (anyOf, allOf, noneOf), views (atLeast or below, per video), totalViews (atLeast or below, the creator's total views) and totalVideos (atLeast or below, the creator's number of videos). On a per-video payout a video must match to be paid; on an all-content payout only matching videos' views add up. Views conditions work per video: base payouts count a video in the period it reaches an atLeast threshold (countWhen reached, the default) or only by the end of its publication period (countWhen byPublicationPeriodEnd, the same as minViews, and the only variant of a base below); per-video CPM payouts and bonuses pay a video's earlier views once it reaches an atLeast threshold, and pay a below one only for periods the video ends under the count. totalViews judges each payout period on the creator's total views over the payout's platforms, whatever the other conditions say (countFrom periodVideos: views of the videos published in the period, counted to its end; eligibleVideos: views gained in the period by every video inside its eligibility window): a period that misses pays nothing through this payout, and its views are never paid later. totalVideos judges each payout period on every video the creator published in it, on every platform (a post on three platforms counts 3), whatever the other conditions and the payout's platforms say; its value is a count or "videoTarget" (the campaign's video target, or the creator's own with custom rates, never prorated, counted in the target's unit: under creatives each video once whatever platforms it is on, under per_platform every listed platform must reach its own count and other platforms do not count). A period that misses pays nothing through this payout; on a base payout an atLeast row may carry the missed period's videos instead (carry previousPeriod: paid in the next period if that one reaches its own minimum; untilPaid: every missed period waits for the first that does), priced as if paid in the period they were posted in (its rate steps, its end and its base payout cap, never the paying period's), only directly in a match all group. One atLeast and one below make a range. Periods follow the campaign's schedule.timeZone; views are counted per UTC day. The platform list always applies on top.
        - `match` 'all' | 'any': all: a video must meet every condition. any: one condition is enough.
        - `items` union[], required
          - union
            - object
              - …
            - object
              - …
            - object
              - …
            - object
              - …
            - object
              - …
            - object
              - …
      - `tiers` object[], required
        - `id` string: Stable id of the stored rule (read it from GET). Send it back to keep the rule, and any creator's custom rate on it, attached through edits; omit it for new entries.
        - `from` integer, required: Views where the tier starts (inclusive).
        - `to` integer, nullable: Views where the tier ends (exclusive); the next tier's from must equal it. Omit or null on the last tier for an open end.
        - `rate` number, required: Paid per 1,000 views in this tier.
    - `flatBonuses` object[]
      - `scope` 'per_video' | 'per_creative' | 'all_content': per_video: applied to every video with its own views. per_creative: copies of one video posted to several platforms (grouped as a creative) count as one video, their views added up; a video in no creative counts on its own. all_content: applied once per cycle to the creator's aggregated views.
      - `creatives` 'bestInPeriod' | 'bestInWindow': Pay only each creative's winner: when the creator posts the same video to several of this payout's platforms (copies grouped as a creative), only the copy with the most views earns it, judged in the payout period (bestInPeriod) or in each copy's eligibility window (bestInWindow). A creative is never paid twice. Needs two or more platforms; not with scope per_creative or a current_period_only eligibility; bonuses per video take bestInWindow only, and CPM payouts per video with an atLeast views condition too. Omit to pay every copy.
      - `mode` 'highest' | 'aggregated': highest: only the highest unlocked tier is paid. aggregated: every unlocked tier adds its amount. Defaults to highest with two or more tiers or a tier that carries `to`, aggregated for a single open-ended tier.
      - `platforms` string[]: Platforms this entry applies to. Omit for all platforms. Entries of the same family may share platforms: a video on a shared platform earns each entry that covers it, and the dry-run warns.
      - `conditions` object: Which videos take part in this payout: content type (video/slideshow), tags (custom, project or geography tag ids; anyOf, allOf, noneOf), hashtags (anyOf, allOf, noneOf), views (atLeast or below, per video), totalViews (atLeast or below, the creator's total views) and totalVideos (atLeast or below, the creator's number of videos). On a per-video payout a video must match to be paid; on an all-content payout only matching videos' views add up. Views conditions work per video: base payouts count a video in the period it reaches an atLeast threshold (countWhen reached, the default) or only by the end of its publication period (countWhen byPublicationPeriodEnd, the same as minViews, and the only variant of a base below); per-video CPM payouts and bonuses pay a video's earlier views once it reaches an atLeast threshold, and pay a below one only for periods the video ends under the count. totalViews judges each payout period on the creator's total views over the payout's platforms, whatever the other conditions say (countFrom periodVideos: views of the videos published in the period, counted to its end; eligibleVideos: views gained in the period by every video inside its eligibility window): a period that misses pays nothing through this payout, and its views are never paid later. totalVideos judges each payout period on every video the creator published in it, on every platform (a post on three platforms counts 3), whatever the other conditions and the payout's platforms say; its value is a count or "videoTarget" (the campaign's video target, or the creator's own with custom rates, never prorated, counted in the target's unit: under creatives each video once whatever platforms it is on, under per_platform every listed platform must reach its own count and other platforms do not count). A period that misses pays nothing through this payout; on a base payout an atLeast row may carry the missed period's videos instead (carry previousPeriod: paid in the next period if that one reaches its own minimum; untilPaid: every missed period waits for the first that does), priced as if paid in the period they were posted in (its rate steps, its end and its base payout cap, never the paying period's), only directly in a match all group. One atLeast and one below make a range. Periods follow the campaign's schedule.timeZone; views are counted per UTC day. The platform list always applies on top.
        - `match` 'all' | 'any': all: a video must meet every condition. any: one condition is enough.
        - `items` union[], required
          - union
            - object
              - …
            - object
              - …
            - object
              - …
            - object
              - …
            - object
              - …
            - object
              - …
      - `tiers` object[], required
        - `id` string: Stable id of the stored rule (read it from GET). Send it back to keep the rule, and any creator's custom rate on it, attached through edits; omit it for new entries.
        - `from` integer, required: Views where the tier starts (inclusive).
        - `to` integer, nullable: Views where the tier ends (exclusive); the next tier's from must equal it. Omit or null on the last tier for an open end.
        - `amount` number, required: Bonus paid when the tier is reached.
    - `baseCap` object: The most all base payouts together pay per cycle, rate steps included.
      - `id` string: Stable id of the stored rule (read it from GET). Send it back to keep the rule, and any creator's custom rate on it, attached through edits; omit it for new entries.
      - `amount` number, required: Amount in major units of the campaign currency (10 = 10.00).
    - `performanceCap` object: Maximum combined CPM payouts and flat bonuses per cycle. Base payout and fixed salary are not affected. Needs at least one CPM payout or flat bonus.
      - `id` string: Stable id of the stored rule (read it from GET). Send it back to keep the rule, and any creator's custom rate on it, attached through edits; omit it for new entries.
      - `amount` number, required: Amount in major units of the campaign currency (10 = 10.00).
    - `eligibility` object: How long and which videos count toward performance payouts.
      - `windowDays` integer: Days after publication a video keeps counting toward performance payouts.
      - `publicationScope` 'include_earlier' | 'current_period_only': include_earlier: videos published before the period still count while inside their window. current_period_only: only videos published within the period count, and windows are cut off at period end.
  - `notifyCreatorsOfBriefChange` boolean: When briefId links a (different) brief, notify the campaign's assigned creators about it. Removals never notify.
  - `previewHash` string: The previewHash an edit dry-run returned for these exact changes. When present, a merged spec whose hash differs is refused (CAMPAIGN_PREVIEW_MISMATCH).
  - `resetRemovedCustomRates` boolean: Consent to reset creators' custom rates on payouts this edit removes (the dry-run lists them in customRateImpact.resets). Without it such an edit fails with CUSTOM_RATES_RESET_REQUIRED.
  - `applyToCustomRates` object[]: Values this edit changes that creators have their own of, to apply to them too: their custom value resets and they follow the campaign's new one. Addresses come from the dry-run's customRateImpact.overridden.
    - `id` union
      - string
      - 'tiktok' | 'instagram' | 'youtube' | 'facebook' | 'snapchat'
    - `field` 'perVideo' | 'videoCap' | 'amountCap' | 'rate' | 'amount' | 'videoTarget', required

## Response `200`

OK

- object: A campaign with its spec, the sentences describing every rule-set, the sheet's alerts, locked fields and any advanced rules.
  - `id` string, required: Campaign ID with orgcamp_ prefix
  - `name` string, required
  - `description` string, nullable, required
  - `currency` 'USD' | 'EUR' | 'CHF' | 'GBP' | 'CNY' | 'JPY' | 'KRW' | 'THB' | 'IDR' | 'VND' | 'RUB', required
  - `interval` 'day' | 'week' | 'month', required
  - `intervalCount` integer, required
  - `billingAnchor` 'rolling' | 'fixed', required
  - `timeZone` string, 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.
  - `videoTarget` integer, 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).
  - `videoTargetPlatforms` object, nullable, required: Videos per platform per cycle, with videoTargetUnit "per_platform"; null otherwise (create and update read a null sent back as none).
  - `validFrom` string, date-time, required
  - `validUntil` string, 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.
  - `createdAt` string, date-time, required
  - `updatedAt` string, date-time, required
  - `orgId` string, required: Organization ID with org_ prefix
  - `userId` string, required: User ID with user_ prefix
  - `actorType` 'user' | 'system' | 'api_token', required
  - `rules` unknown[], required: The raw payout rules as the engine stores them. Kept for existing integrations; new ones read `spec` and write through the spec endpoints.
    - unknown
  - `spec` object, required: A campaign spec with every default filled in.
    - `name` string, required: Campaign name
    - `description` string: Campaign description
    - `currency` 'USD' | 'EUR' | 'CHF' | 'GBP' | 'CNY' | 'JPY' | 'KRW' | 'THB' | 'IDR' | 'VND' | 'RUB', required: Currency used for campaign payouts
    - `schedule` object, required: Payout cadence and window.
      - `interval` 'day' | 'week' | 'month', required: Payout cycle unit. Monthly = month × 1, biweekly = week × 2, weekly = week × 1.
      - `intervalCount` number, required: How many units make one payout cycle.
      - `anchor` 'rolling' | 'fixed', required: rolling: each creator's cycle starts the day they join, so creators are added and paid on their own dates. fixed: everyone shares the same calendar cycle and payouts go out in one batch per cycle; a creator added mid-cycle gets that cycle as a first period cut to the join day, a fixed salary in it paid pro rata. Required — it locks once the campaign is in use.
      - `startsOn` string, required: Campaign start (YYYY-MM-DD).
      - `timeZone` string: IANA time zone the periods and dates follow: a period ends at midnight there, a video belongs to the period it was published in there, and "today" is the zone's day. Views are still counted per UTC day. Defaults to "UTC" on create; editable until a payout depends on its periods or a period is canceled (`CAMPAIGN_FIELD_LOCKED`).
    - `videoTarget` number, 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).
    - `videoTargetPlatforms` object, nullable: Videos per platform per cycle, with videoTargetUnit "per_platform".
    - `briefId` string, nullable: Campaign brief ID with orgbrief_ prefix
    - `payouts` object, required
      - `fixedSalary` object: A guaranteed amount per cycle, regardless of videos.
        - `id` string: Stable id of the stored rule (read it from GET). Send it back to keep the rule, and any creator's custom rate on it, attached through edits; omit it for new entries.
        - `amount` number, required: Amount in major units of the campaign currency (10 = 10.00).
      - `basePayouts` object[]
        - `id` string: Stable id of the stored rule (read it from GET). Send it back to keep the rule, and any creator's custom rate on it, attached through edits; omit it for new entries.
        - `perVideo` number, required: Paid per published video (until the first step).
        - `steps` object[]: Rate steps: a video is paid the rate of the step its place in the period falls in. An update that omits them keeps them; [] removes them.
          - `id` string: Stable id of the stored rule (read it from GET). Send it back to keep the rule, and any creator's custom rate on it, attached through edits; omit it for new entries.
          - `startsAfter` union, required: Videos before the step: a count or "videoTarget". Rises and stays below the end.
            - number
            - 'videoTarget'
          - `perVideo` number, required: Paid per video from this step on.
        - `limit` object, nullable: Omitted: the base payout stops at the video target. null: unlimited, every extra video is paid.
          - `videos` union: Videos paid per cycle: a count (above the target: an overshoot allowance), or "videoTarget".
            - number
            - 'videoTarget'
          - `amount` number: Deprecated: payouts.baseCap. Only with one base payout; without videos only this cap limits it.
        - `minViews` number: Only videos reaching this many total views by the end of the payout period they were published in earn the base payout. The same as a views condition with countWhen byPublicationPeriodEnd; send one or the other.
        - `platforms` string[]: Platforms this entry applies to. Omit for all platforms. Entries of the same family may share platforms: a video on a shared platform earns each entry that covers it, and the dry-run warns.
        - `conditions` object: Which videos take part in this payout: content type (video/slideshow), tags (custom, project or geography tag ids; anyOf, allOf, noneOf), hashtags (anyOf, allOf, noneOf), views (atLeast or below, per video), totalViews (atLeast or below, the creator's total views) and totalVideos (atLeast or below, the creator's number of videos). On a per-video payout a video must match to be paid; on an all-content payout only matching videos' views add up. Views conditions work per video: base payouts count a video in the period it reaches an atLeast threshold (countWhen reached, the default) or only by the end of its publication period (countWhen byPublicationPeriodEnd, the same as minViews, and the only variant of a base below); per-video CPM payouts and bonuses pay a video's earlier views once it reaches an atLeast threshold, and pay a below one only for periods the video ends under the count. totalViews judges each payout period on the creator's total views over the payout's platforms, whatever the other conditions say (countFrom periodVideos: views of the videos published in the period, counted to its end; eligibleVideos: views gained in the period by every video inside its eligibility window): a period that misses pays nothing through this payout, and its views are never paid later. totalVideos judges each payout period on every video the creator published in it, on every platform (a post on three platforms counts 3), whatever the other conditions and the payout's platforms say; its value is a count or "videoTarget" (the campaign's video target, or the creator's own with custom rates, never prorated, counted in the target's unit: under creatives each video once whatever platforms it is on, under per_platform every listed platform must reach its own count and other platforms do not count). A period that misses pays nothing through this payout; on a base payout an atLeast row may carry the missed period's videos instead (carry previousPeriod: paid in the next period if that one reaches its own minimum; untilPaid: every missed period waits for the first that does), priced as if paid in the period they were posted in (its rate steps, its end and its base payout cap, never the paying period's), only directly in a match all group. One atLeast and one below make a range. Periods follow the campaign's schedule.timeZone; views are counted per UTC day. The platform list always applies on top.
          - `match` 'all' | 'any', required
          - `items` union[], required
            - union
              - …
        - `scope` 'per_video' | 'per_creative': per_video (the default): every video counts. per_creative: copies of one video posted to several of this payout's platforms (grouped as a creative) count as one creative, their views added up: it earns one base payout, and views conditions judge the creative's combined views (directly in a match all group, never next to a totalVideos row that carries). It counts in the first period at whose end its views meet a reached atLeast condition, otherwise only in the period its first copy was posted in. Needs two or more platforms; not with creatives once.
        - `creatives` 'once': once: only each creative's winner counts. When the creator posts the same video to several of this payout's platforms (copies grouped as a creative), the copy with the most views earns one base payout, in the first period any copy is counted; views conditions judge each copy on its own. Needs two or more platforms; not with scope per_creative. Omit to count every copy.
      - `cpmPayouts` object[]
        - `scope` 'per_video' | 'per_creative' | 'all_content': per_video: applied to every video with its own views. per_creative: copies of one video posted to several platforms (grouped as a creative) count as one video, their views added up; a video in no creative counts on its own. all_content: applied once per cycle to the creator's aggregated views.
        - `creatives` 'bestInPeriod' | 'bestInWindow': Pay only each creative's winner: when the creator posts the same video to several of this payout's platforms (copies grouped as a creative), only the copy with the most views earns it, judged in the payout period (bestInPeriod) or in each copy's eligibility window (bestInWindow). A creative is never paid twice. Needs two or more platforms; not with scope per_creative or a current_period_only eligibility; bonuses per video take bestInWindow only, and CPM payouts per video with an atLeast views condition too. Omit to pay every copy.
        - `platforms` string[]: Platforms this entry applies to. Omit for all platforms. Entries of the same family may share platforms: a video on a shared platform earns each entry that covers it, and the dry-run warns.
        - `conditions` object: Which videos take part in this payout: content type (video/slideshow), tags (custom, project or geography tag ids; anyOf, allOf, noneOf), hashtags (anyOf, allOf, noneOf), views (atLeast or below, per video), totalViews (atLeast or below, the creator's total views) and totalVideos (atLeast or below, the creator's number of videos). On a per-video payout a video must match to be paid; on an all-content payout only matching videos' views add up. Views conditions work per video: base payouts count a video in the period it reaches an atLeast threshold (countWhen reached, the default) or only by the end of its publication period (countWhen byPublicationPeriodEnd, the same as minViews, and the only variant of a base below); per-video CPM payouts and bonuses pay a video's earlier views once it reaches an atLeast threshold, and pay a below one only for periods the video ends under the count. totalViews judges each payout period on the creator's total views over the payout's platforms, whatever the other conditions say (countFrom periodVideos: views of the videos published in the period, counted to its end; eligibleVideos: views gained in the period by every video inside its eligibility window): a period that misses pays nothing through this payout, and its views are never paid later. totalVideos judges each payout period on every video the creator published in it, on every platform (a post on three platforms counts 3), whatever the other conditions and the payout's platforms say; its value is a count or "videoTarget" (the campaign's video target, or the creator's own with custom rates, never prorated, counted in the target's unit: under creatives each video once whatever platforms it is on, under per_platform every listed platform must reach its own count and other platforms do not count). A period that misses pays nothing through this payout; on a base payout an atLeast row may carry the missed period's videos instead (carry previousPeriod: paid in the next period if that one reaches its own minimum; untilPaid: every missed period waits for the first that does), priced as if paid in the period they were posted in (its rate steps, its end and its base payout cap, never the paying period's), only directly in a match all group. One atLeast and one below make a range. Periods follow the campaign's schedule.timeZone; views are counted per UTC day. The platform list always applies on top.
          - `match` 'all' | 'any', required
          - `items` union[], required
            - union
              - …
        - `tiers` object[], required
          - `id` string: Stable id of the stored rule (read it from GET). Send it back to keep the rule, and any creator's custom rate on it, attached through edits; omit it for new entries.
          - `from` number, required: Views where the tier starts (inclusive).
          - `to` number, nullable: Views where the tier ends (exclusive); the next tier's from must equal it. Omit or null on the last tier for an open end.
          - `rate` number, required: Paid per 1,000 views in this tier.
      - `flatBonuses` object[]
        - `scope` 'per_video' | 'per_creative' | 'all_content': per_video: applied to every video with its own views. per_creative: copies of one video posted to several platforms (grouped as a creative) count as one video, their views added up; a video in no creative counts on its own. all_content: applied once per cycle to the creator's aggregated views.
        - `creatives` 'bestInPeriod' | 'bestInWindow': Pay only each creative's winner: when the creator posts the same video to several of this payout's platforms (copies grouped as a creative), only the copy with the most views earns it, judged in the payout period (bestInPeriod) or in each copy's eligibility window (bestInWindow). A creative is never paid twice. Needs two or more platforms; not with scope per_creative or a current_period_only eligibility; bonuses per video take bestInWindow only, and CPM payouts per video with an atLeast views condition too. Omit to pay every copy.
        - `mode` 'highest' | 'aggregated': highest: only the highest unlocked tier is paid. aggregated: every unlocked tier adds its amount. Defaults to highest with two or more tiers or a tier that carries `to`, aggregated for a single open-ended tier.
        - `platforms` string[]: Platforms this entry applies to. Omit for all platforms. Entries of the same family may share platforms: a video on a shared platform earns each entry that covers it, and the dry-run warns.
        - `conditions` object: Which videos take part in this payout: content type (video/slideshow), tags (custom, project or geography tag ids; anyOf, allOf, noneOf), hashtags (anyOf, allOf, noneOf), views (atLeast or below, per video), totalViews (atLeast or below, the creator's total views) and totalVideos (atLeast or below, the creator's number of videos). On a per-video payout a video must match to be paid; on an all-content payout only matching videos' views add up. Views conditions work per video: base payouts count a video in the period it reaches an atLeast threshold (countWhen reached, the default) or only by the end of its publication period (countWhen byPublicationPeriodEnd, the same as minViews, and the only variant of a base below); per-video CPM payouts and bonuses pay a video's earlier views once it reaches an atLeast threshold, and pay a below one only for periods the video ends under the count. totalViews judges each payout period on the creator's total views over the payout's platforms, whatever the other conditions say (countFrom periodVideos: views of the videos published in the period, counted to its end; eligibleVideos: views gained in the period by every video inside its eligibility window): a period that misses pays nothing through this payout, and its views are never paid later. totalVideos judges each payout period on every video the creator published in it, on every platform (a post on three platforms counts 3), whatever the other conditions and the payout's platforms say; its value is a count or "videoTarget" (the campaign's video target, or the creator's own with custom rates, never prorated, counted in the target's unit: under creatives each video once whatever platforms it is on, under per_platform every listed platform must reach its own count and other platforms do not count). A period that misses pays nothing through this payout; on a base payout an atLeast row may carry the missed period's videos instead (carry previousPeriod: paid in the next period if that one reaches its own minimum; untilPaid: every missed period waits for the first that does), priced as if paid in the period they were posted in (its rate steps, its end and its base payout cap, never the paying period's), only directly in a match all group. One atLeast and one below make a range. Periods follow the campaign's schedule.timeZone; views are counted per UTC day. The platform list always applies on top.
          - `match` 'all' | 'any', required
          - `items` union[], required
            - union
              - …
        - `tiers` object[], required
          - `id` string: Stable id of the stored rule (read it from GET). Send it back to keep the rule, and any creator's custom rate on it, attached through edits; omit it for new entries.
          - `from` number, required: Views where the tier starts (inclusive).
          - `to` number, nullable: Views where the tier ends (exclusive); the next tier's from must equal it. Omit or null on the last tier for an open end.
          - `amount` number, required: Bonus paid when the tier is reached.
      - `baseCap` object: The most all base payouts together pay per cycle, rate steps included.
        - `id` string: Stable id of the stored rule (read it from GET). Send it back to keep the rule, and any creator's custom rate on it, attached through edits; omit it for new entries.
        - `amount` number, required: Amount in major units of the campaign currency (10 = 10.00).
      - `performanceCap` object: Maximum combined CPM payouts and flat bonuses per cycle. Base payout and fixed salary are not affected. Needs at least one CPM payout or flat bonus.
        - `id` string: Stable id of the stored rule (read it from GET). Send it back to keep the rule, and any creator's custom rate on it, attached through edits; omit it for new entries.
        - `amount` number, required: Amount in major units of the campaign currency (10 = 10.00).
      - `eligibility` object: How long and which videos count toward performance payouts.
        - `windowDays` number: Days after publication a video keeps counting toward performance payouts.
        - `publicationScope` 'include_earlier' | 'current_period_only': include_earlier: videos published before the period still count while inside their window. current_period_only: only videos published within the period count, and windows are cut off at period end.
  - `headline` string, required: One line: payouts and cadence, e.g. “€10.00 / video + CPM €1.00–€1.50 · monthly, rolling”.
  - `compensation` object, required: Min/max amount per payout family (per video, fixed salary, CPM, bonus).
    - `basePayoutPerVideo` object
      - `min` number, required
      - `max` number, required
    - `basePayoutUnit` 'video' | 'creative' | 'mixed': What basePayoutPerVideo pays for: each video (every copy of a video posted to several platforms), each creative (its copies count once), or mixed (base payouts of both kinds). Present with basePayoutPerVideo.
    - `fixedSalary` number
    - `cpmPayout` object
      - `min` number, required
      - `max` number, required
    - `flatBonus` object
      - `min` number, required
      - `max` number, required
  - `createdByUser` object, nullable, required
    - `id` string, required: User ID with user_ prefix
    - `name` string, nullable, required
    - `email` string, required
    - `image` string, nullable, required
  - `creatorSummary` object, required: Creator summary scoped to current or scheduled assignments (valid_until is NULL or in the future). totalCount still reflects all historical assignments.
    - `totalCount` integer, required
    - `activeCount` integer, required
    - `activeCreators` object[], required
      - `id` string, required: Organization creator ID with orgcre_ prefix
      - `user` object, required
        - `id` string, required: User ID with user_ prefix
        - `name` string, nullable, required
        - `email` string, required
        - `image` string, nullable, required
  - `hasPaidPeriods` boolean: 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`).
  - `brief` object, nullable, required
    - `id` string, required: Campaign brief ID with orgbrief_ prefix
    - `name` string, required
  - `warnings` union[], required
    - union
      - object
        - `type` 'tracking_limit_low', required
        - `creator` object, required
          - `id` string, required: Organization creator ID with orgcre_ prefix
          - `user` object, required
            - `id` string, required: User ID with user_ prefix
            - `name` string, nullable, required
            - `email` string, required
            - `image` string, nullable, required
        - `videoTarget` integer, required
        - `requiredMaxVideos` integer, required
        - `accounts` object[], required
          - `id` string, required: Organization account ID with orgacc_ prefix. Not the same as platform account ID.
          - `platform` 'facebook' | 'instagram' | 'tiktok' | 'youtube' | 'snapchat', required: Social media platform (facebook, instagram, tiktok, youtube, snapchat)
          - `platformAccountId` string, required: Native platform account id
          - `username` string, nullable, required
          - `maxVideos` integer, required
          - `suggestedMaxVideos` integer, nullable, required
      - object: Accounts with a tracking limit of 0 that hold individually tracked videos: only those count, new uploads are missed
        - `type` 'manual_video_tracking', required
        - `creator` object, required
          - `id` string, required: Organization creator ID with orgcre_ prefix
          - `user` object, required
            - `id` string, required: User ID with user_ prefix
            - `name` string, nullable, required
            - `email` string, required
            - `image` string, nullable, required
        - `videoTarget` integer, required
        - `requiredMaxVideos` integer, required
        - `accounts` object[], required
          - `id` string, required: Organization account ID with orgacc_ prefix. Not the same as platform account ID.
          - `platform` 'facebook' | 'instagram' | 'tiktok' | 'youtube' | 'snapchat', required: Social media platform (facebook, instagram, tiktok, youtube, snapchat)
          - `platformAccountId` string, required: Native platform account id
          - `username` string, nullable, required
          - `maxVideos` integer, required
          - `suggestedMaxVideos` integer, nullable, required
      - object: Accounts whose tracking is paused (a limit of 0 without individually tracked videos): nothing new syncs
        - `type` 'tracking_paused', required
        - `creator` object, required
          - `id` string, required: Organization creator ID with orgcre_ prefix
          - `user` object, required
            - `id` string, required: User ID with user_ prefix
            - `name` string, nullable, required
            - `email` string, required
            - `image` string, nullable, required
        - `videoTarget` integer, required
        - `requiredMaxVideos` integer, required
        - `accounts` object[], required
          - `id` string, required: Organization account ID with orgacc_ prefix. Not the same as platform account ID.
          - `platform` 'facebook' | 'instagram' | 'tiktok' | 'youtube' | 'snapchat', required: Social media platform (facebook, instagram, tiktok, youtube, snapchat)
          - `platformAccountId` string, required: Native platform account id
          - `username` string, nullable, required
          - `maxVideos` integer, required
          - `suggestedMaxVideos` integer, nullable, required
  - `customRates` object, nullable: Custom rates on this campaign: creators paid with their own values for some of its payouts. Read one creator's values with GET /campaigns/{id}/custom-rates/{creatorId}.
    - `creatorIds` string[], required: Creators with their own values on this campaign.
    - `columns` object, required: Per part of the terms, how many of those creators have a value of their own in it (caps: video and amount caps of base payouts and the performance cap).
      - `videoTarget` integer, required
      - `basePayout` integer, required
      - `fixedSalary` integer, required
      - `cpmPayout` integer, required
      - `flatBonus` integer, required
      - `caps` integer, required
  - `conditionTagNames` object: 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.
  - `summary` object, required: The campaign's rules in plain sentences, one per rule-set — the same wording the campaign sheet shows.
    - `schedule` string, required
    - `videoTarget` string: What the video target counts, when it is not every post: each video once (videoTargetUnit creatives) or a count per platform (per_platform). Absent for a videos target, whose count needs no sentence.
    - `fixedSalary` string
    - `basePayouts` string[], required
    - `baseCap` string
    - `cpmPayouts` string[], required
    - `flatBonuses` string[], required
    - `performanceCap` string
    - `eligibility` string
    - `text` string, required: Every sentence above, in reading order.
    - `headline` string, required: One line: payouts and cadence, e.g. “€10.00 / video + CPM €1.00–€1.50 · monthly, rolling”.
  - `specWarnings` object[], required: The campaign sheet's inline alerts for this configuration. Never blocking.
    - `code` 'long_payout_cycle' | 'daily_cadence' | 'future_start' | 'eligibility_window_exceeds_interval' | 'flat_bonus_waits_for_window' | 'bonus_tiers_unpaid_after_end' | 'performance_cap_below_bonus' | 'base_payouts_overlap' | 'cpm_payouts_overlap' | 'flat_bonuses_overlap' | 'manual_payouts_only' | 'rules_apply_immediately' | 'views_conditions_changed_after_payouts' | 'total_videos_changed_after_payouts' | 'payout_conditions_removed' | 'creative_payouts_removed' | 'time_zone_changed' | 'derived_from_job' | 'video_target_followers_frozen', required
    - `title` string, required
    - `message` string, required
  - `lockedFields` string[], required: Spec fields that can no longer change because the campaign has creator assignments or payout history. Sending their current value still passes.
  - `advancedRules` union[], required: Payout rules outside the spec's vocabulary, set up internally. They survive every update untouched and are not editable through the API.
    - union
      - object
        - `version` unknown, required
        - `id` string, required
        - `kind` 'fixed', required
        - `name` 'fixed_salary', required
        - `priority` integer, required
        - `payout` object, required
          - `type` 'flat', required
          - `amount` integer, required
          - `precision` integer, required
      - object
        - `version` unknown, required
        - `id` string, required
        - `kind` 'metric', required
        - `name` string, required
        - `priority` integer, required
        - `fact` union, required
          - object
            - `type` 'metric', required
            - `metric` 'view_gain' | 'like_gain' | 'comment_gain' | 'share_gain' | 'video_count', required
            - `grain` 'aggregate' | 'per_video' | 'per_platform' | 'per_account', required
            - `aggregation` 'sum' | 'avg' | 'count' | 'min' | 'max', required
            - `window` object, required
              - …
            - `publicationScope` 'include_earlier' | 'billing_period_only', required
            - `assignmentPublicationMode` 'assigned_only' | 'include_pre_assignment'
            - `eligibility` object, required
              - …
            - `filters` object
              - …
          - object
            - `type` 'snapshot_ratio', required
            - `metric` 'engagement_rate', required
            - `formula` 'engagement_actions_per_view', required
            - `grain` 'aggregate' | 'per_video' | 'per_platform' | 'per_account', required
            - `window` object, required
              - …
            - `publicationScope` 'include_earlier' | 'billing_period_only', required
            - `assignmentPublicationMode` 'assigned_only' | 'include_pre_assignment'
            - `cap` unknown, required
            - `eligibility` object, required
              - …
            - `filters` object
              - …
        - `tier` object, required
          - `index` integer
          - `min` object, required
            - `value` number, required
            - `bound` 'exclusive' | 'inclusive', required
            - `followsTarget` unknown
          - `max` object, nullable, required
            - `value` number, required
            - `bound` 'exclusive' | 'inclusive', required
            - `followsTarget` unknown
        - `payout` union, required
          - object
            - `type` 'flat', required
            - `amount` integer, required
            - `precision` integer, required
            - `mode` 'allocation' | 'progressive', required
            - `timing` 'gains' | 'milestone', required
          - object
            - `type` 'rate', required
            - `amount` integer, required
            - `precision` integer, required
            - `perQuantity` integer, required
            - `mode` 'allocation' | 'progressive', required
            - `maxPayout` integer
      - object
        - `version` unknown, required
        - `id` string, required
        - `kind` 'cap', required
        - `name` 'performance_total_cap', required
        - `priority` integer, required
        - `appliesTo` 'performance', required
        - `cap` object, required
          - `amount` integer, required
          - `precision` integer, required
      - object
        - `version` unknown, required
        - `id` string, required
        - `kind` 'cap', required
        - `name` 'base_total_cap', required
        - `priority` integer, required
        - `appliesTo` 'base', required
        - `cap` object, required
          - `amount` integer, required
          - `precision` integer, required

## Other responses

- `400`: 400
- `401`: 401
- `402`: 402
- `404`: 404
- `408`: 408
- `409`: 409
- `429`: 429
- `500`: 500

---

[API](https://skmtc.dev/viral/apis/viral-app-api.md) · [All operations](https://skmtc.dev/viral/apis/viral-app-api/llms.txt) · [OpenAPI document](https://skmtc.dev/viral/apis/viral-app-api/revisions/31e97fa79ef1?raw)
