---
title: "Update an existing campaign"
method: PATCH
path: "/campaigns/{campaign_id}"
tags: ["Campaigns"]
---

# Update an existing campaign

`PATCH /campaigns/{campaign_id}`

Modify campaign fields. `campaign_type` and `creator_source` are immutable post-create — pass them and you'll get 400 IMMUTABLE_FIELD.

## Path parameters

- `campaign_id` integer, required

## Headers

- `Idempotency-Key` string, nullable
- `X-Dry-Run` string, nullable

## Request body

- CampaignUpdateRequest — ``PATCH /public/v1/campaigns/{campaign_id}``. Mirrors ``community.schemas.campaign.CampaignUpdateRequest`` minus ``shop_id`` and ``campaign_id`` (both injected — shop from header, campaign from path). ``campaign_type`` and ``creator_source`` are immutable post-create and are intentionally not in this shape; the portal rejects them too.
  - `campaign_name` string, nullable
  - `start_date` string, nullable
  - `end_date` string, nullable
  - `description` string, nullable
  - `status` 'published' | 'draft' | 'deleted' | 'archived' — DB-level campaign status. The frontend filter param 'active'/'past' are UI aliases — 'active' maps to published, 'past' maps to != published.
  - `pricing` union
    - RetainerPricing — Pricing configuration for retainer campaigns.
      - `campaign_type` 'retainer'
      - `posts` integer, required — Required posts per creator
      - `payment_per_creator` union, required — Payment amount (float for cash, str for custom)
        - number
        - string
      - `reward_type` 'cash' | 'custom', required — Reward payout mechanism.
      - `metric_type` 'gmv' | 'views' | 'videos_posted' | 'gmv_videos' — Performance metric to evaluate
      - `minimum_gmv_required` number, nullable — Minimum GMV threshold
      - `minimum_gmv_required_enabled` boolean — Whether minimum GMV requirement is active
      - `minimum_views_required` integer, nullable — Minimum views threshold
      - `minimum_views_required_enabled` boolean — Whether minimum views requirement is active
      - `minimum_videos_required` integer, nullable — Minimum videos threshold (for videos_posted/gmv_videos metric)
    - ChallengePricing — Pricing configuration for challenge campaigns.
      - `campaign_type` 'challenge'
      - `payment_tiers` PaymentTier[], required — At least one tier required
        - `threshold` number, required — Metric threshold for this tier
        - `compensation` union, required — Payout at this tier
          - number
          - string
        - `videos_required` integer, nullable — Number of videos required for this tier
      - `reward_type` 'cash' | 'custom', required — Reward payout mechanism.
      - `metric_type` 'gmv' | 'views' | 'videos_posted' | 'gmv_videos' — Performance metric to evaluate
      - `minimum_gmv_required` number, nullable — Minimum GMV floor (gmv_videos gate)
      - `minimum_gmv_required_enabled` boolean — Whether the minimum GMV floor is active
      - `minimum_views_required` integer, nullable — Minimum views floor (when metric is views)
      - `minimum_videos_required` integer, nullable — Minimum videos floor (videos_posted / gmv_videos gate)
      - `ranking_priority` 'gmv' | 'videos', nullable — Primary ranking metric for gmv_videos contests; the other is a per-tier requirement
    - LeaderboardPricing — Pricing configuration for leaderboard campaigns. Extra keys are IGNORED (the codebase default, matching every sibling pricing class): the brand FE sends UI-only toggles like ``minimum_gmv_required_enabled`` that are not modelled here. The smuggling protection the materializer needs comes from re-parsing the stored template pricing through this model, which drops unknown keys so they never reach a materialized instance — not from ``extra="forbid"``, which would break the live create/edit path.
      - `campaign_type` 'leaderboard'
      - `leaderboard_ranks` LeaderboardRank[], required — At least one rank required
        - `rank` integer, required — Rank position (1-indexed)
        - `reward` union, required — Reward for this rank
          - number
          - string
      - `reward_type` 'cash' | 'custom', required — Reward payout mechanism.
      - `metric_type` 'gmv' | 'views' | 'videos_posted' | 'gmv_videos' | 'live_gmv' | 'gmv_and_live_gmv' | 'videos_gmv_and_live_gmv' — Performance metric to evaluate
      - `scoring_mode` 'single' | 'weighted' — Rank by a single metric or by a weighted composite score
      - `weighted_metrics` WeightedMetricItem[], nullable — Metric weights (must sum to 100) when scoring_mode is weighted
        - `metric_type` 'gmv' | 'avg_views_per_post' | 'views' | 'videos_posted' | 'units_sold' | 'live_gmv' | 'combined_gmv' | 'sessions', required — Metrics that can carry a weight in a weighted leaderboard. ``live_gmv`` is campaign-product-scoped LIVE GMV and ``combined_gmv`` is video GMV + that LIVE GMV as one dollar figure; a campaign weighting either must define ``product_ids`` (validated in LeaderboardPricing).
        - `weight` integer, required
      - `minimum_gmv_required` number, nullable — Minimum GMV threshold (when metric includes gmv)
      - `minimum_views_required` integer, nullable — Minimum views threshold (when metric is views)
      - `minimum_videos_required` integer, nullable — Minimum videos floor (gmv_videos gate; ranking stays by GMV)
      - `minimum_live_gmv_required` number, nullable — Minimum LIVE GMV floor (when metric is live_gmv)
      - `minimum_sessions_required` integer, nullable — Minimum LIVE sessions floor (weighted gate on a sessions weight)
    - LiveGMVChallengePricing — Pricing configuration for live GMV challenge campaigns.
      - `campaign_type` 'live_gmv_challenge'
      - `payment_tiers` LivePaymentTier[], required — At least one tier required
        - `threshold` number, required — Metric threshold for this tier
        - `compensation` union, required — Payout at this tier
          - number
          - string
        - `videos_required` integer, nullable — Number of videos required for this tier
        - `sessions_required` integer, nullable — Number of live sessions required for this tier (sessions_and_live_gmv metric)
      - `reward_type` 'cash' | 'custom', required — Reward payout mechanism.
      - `metric_type` 'live_gmv' | 'sessions' | 'sessions_and_live_gmv' — Live performance metric to evaluate
      - `minimum_live_gmv_required` number, nullable — Minimum LIVE GMV floor (sessions_and_live_gmv gate)
      - `minimum_sessions_required` integer, nullable — Minimum sessions floor (sessions_and_live_gmv gate)
      - `ranking_priority` 'live_gmv' | 'sessions', nullable — Primary ranking metric for sessions_and_live_gmv contests; the other is a per-tier requirement
    - RacePricing — Shared prize pool, fixed value per qualifying unit, first come first served. ``extra="forbid"``: unlike the legacy types, whose frontend sends UI-only toggles these models never declared, this is a new surface with no legacy payload to tolerate. A misspelled or unsupported payout field must fail loudly rather than be silently dropped -- which is also what makes the minimum-floor rejection real rather than decorative.
      - `campaign_type` 'race'
      - `reward_type` 'cash'
      - `metric_type` 'gmv'
      - `race_metric` 'video' | 'views' | 'live' — The unit a Race funds. Each maps to a distinct candidate query.
      - `race_prize_pool` union, required
        - number
        - string
      - `race_amount_per_unit` union, required
        - number
        - string
      - `race_max_per_creator` union
        - number
        - string
      - `race_hide_budget` boolean
    - BingoPricing — A card of goals, paid per completed line. See RacePricing for extra=forbid.
      - `campaign_type` 'bingo'
      - `reward_type` 'cash'
      - `metric_type` 'gmv'
      - `bingo_grid_size` 3 | 4
      - `bingo_line_length` 3 | 4
      - `bingo_squares` BingoSquare[], required
        - `template` 'total_gmv' | 'single_video_gmv' | 'videos_each_gmv' | 'total_views' | 'single_video_views' | 'videos_each_views' | 'total_likes' | 'single_video_likes' | 'videos_each_likes' | 'video_count' | 'posting_streak' | 'live_count' | 'live_gmv' | 'use_hashtag' | 'feature_product', required — The goals a card square can carry. Every one resolves to a metric already tracked, so a square is verifiable without anyone ticking it by hand.
        - `x` union
          - number
          - string
        - `y` union
          - number
          - string
      - `bingo_line_reward` union, required
        - number
        - string
      - `bingo_payout_rule` 'single' | 'per_line' | 'per_line_capped'
      - `bingo_max_lines` integer, nullable
    - SweepstakesPricing — Points buy entries; one winner drawn weighted by points share. See RacePricing for why extra="forbid".
      - `campaign_type` 'sweepstakes'
      - `reward_type` 'cash'
      - `metric_type` 'gmv'
      - `sweepstakes_prize` union, required
        - number
        - string
      - `sweepstakes_points_per_gmv` union
        - number
        - string
      - `sweepstakes_points_per_view` union
        - number
        - string
      - `sweepstakes_points_per_post` union
        - number
        - string
      - `sweepstakes_draw_trigger` 'auto' | 'manual'
  - `products` CampaignProducts — Product configuration for a campaign.
    - `all_selected` boolean — Whether all shop products are selected
    - `items` ProductItemInput[] — Selected product list
      - `product_id` string, required
      - `tap_link` string, nullable
      - `product_name` string, nullable
    - `use_first_product_image` boolean, nullable — Use first product image as campaign image
    - `custom_image` string, nullable — Custom campaign image (base64 or URL)
    - `campaign_image` string, nullable — Resolved campaign image URL (read-only)
  - `creators` object[], nullable — Optional list of creators to seed into the campaign. Mirrors the portal's contract exactly (see `community.schemas.campaign.CampaignCreate.creators`) so the public API doesn't fork from `/api/marketplace/v2`. Each dict is opaque to validation but is typically `{"creator_id": int, "tier": str | None, "payout_override": int | None}`. Pass `null` or omit for the common case of "no seeded creators" (production fixtures all do this; creators are added later via the accept/reject flow).
  - `window_url` string, nullable
  - `is_available_to_all` boolean, nullable
  - `requires_approval` boolean, nullable
  - `exclusive_participation` boolean, nullable
  - `creator_level` union
    - string[]
    - string
  - `support_email` string, nullable
  - `short_header_message` string, nullable
  - `content_guidelines` object, nullable
  - `inspiration_links` string[], nullable
  - `has_tiered_payout` boolean, nullable
  - `payout_structure` object, nullable
  - `grace_period` integer, nullable
  - `reminders` CampaignReminders — Reminder configuration for a campaign.
    - `enabled` boolean
    - `message_before_3_days` string, nullable
    - `message_before_1_day` string, nullable
  - `email_templates` CampaignEmailTemplates — Email template configuration for a campaign.
    - `enabled` boolean
    - `application` object, nullable
    - `acceptance` object, nullable
    - `rejection` object, nullable

## Response `200`

Successful Response

- CampaignUpdatedResponse — 200 response for ``PATCH /campaigns/{id}``.
  - `campaign_id` integer, required
  - `status` string
  - `participant_sync` ParticipantSync — How a payout-affecting edit was applied to existing participants.
    - `applies_to` string, required — ``existing_and_future``: changed campaign-wide reward terms were re-stamped onto unpaid participants. ``future_only``: the changed payout fields are negotiated per creator and apply to future joiners only.
    - `fields` string[], required — Payout-affecting pricing fields this edit changed.
    - `updated` integer, required — Unpaid participant reward snapshots re-stamped.
    - `unchanged` integer, required — Unpaid snapshots already matching the new terms.
    - `skipped_paid` integer, required — Paid/settled participants left untouched (frozen).

## Other responses

- `422` — Validation Error

---

[API](https://skmtc.dev/reacherapp/apis/reacher-data-api.md) · [All operations](https://skmtc.dev/reacherapp/apis/reacher-data-api/llms.txt) · [OpenAPI document](https://skmtc-service-production.skmtc.workers.dev/v1/apis/reacherapp/reacher-data-api/revisions/d4fe3e380424/schema)
