---
title: "Swap a creator"
method: POST
path: "/plans/{id}/lines/{line_id}/swap"
---

# Swap a creator

`POST /plans/{id}/lines/{line_id}/swap`

## Request

- Base URL: `https://app.distribution.tech/api/v1`
- URL: `https://app.distribution.tech/api/v1/plans/{id}/lines/{line_id}/swap`
- Auth: HTTP bearer

Replace one line of a plan awaiting approval (line_id from plan_quote) with another creator's format (unit_id from creators_search or creator_get) at its current all-in price, in one step: both happen or neither does, so the plan is never left with both creators or with none. The plan's total after the swap must fit the key's mandate and the campaign's budget: a creator at or under the removed creator's price always swaps in, a dearer one that would go over the budget is refused (conflict, reason over_budget, budget_cents, remaining_cents). A creator already on the plan is refused (already_in_plan); to change a creator's format, remove the line and add the format. Returns the new line_id, the removed_line_id and the plan as plan_quote returns it. Needs an idempotency_key. Scope: campaigns:write. MCP tool: plan_swap_creator.

## Path parameters

- `id` string, uuid, required
- `line_id` string, uuid, required

## Headers

- `Idempotency-Key` string, required

## Request body

- LineInput
  - `unit_id` string, uuid, required: A format's unit_id from /creators.

## Response `200`

OK

- LineSwapped
  - `line_id` string, uuid, required
  - `removed_line_id` string, uuid, required
  - `plan` Quote, required: A plan as the person sees it and as an agent signs it (v1-product-spec 8.5 Quote).
    - `plan_id` string, uuid
    - `campaign_id` string, uuid
    - `version` integer
    - `status` 'awaiting_approval' | 'approved'
    - `currency` string
    - `launch_window_start` string, date
    - `launch_window_end` string, date
    - `terms_fingerprint` string: Send exactly this to approve. It changes when a line, a price or the dates change.
    - `total_cents` integer
    - `totals` object: creators, total_cents, views_low, views_high, unmeasured, cpm_low_cents, cpm_high_cents, tiers, budget_cents, remaining_cents.
    - `lines` QuoteLine[]
      - `id` string, uuid, required
      - `creator` object, nullable, required
        - `id` string, uuid: The creator's id, for creator_get. Left out when not known.
        - `name` string, nullable, required
        - `handle` string, nullable, required: The creator's X handle once this plan's order is paid; null before (the name shows before payment).
        - `profile_url` string, nullable, required: The creator's X profile link once this plan's order is paid; null before.
        - `avatar_url` string, uri: The creator's live profile photo. Left out when there is none.
        - `followers` integer: The creator's follower count on the channel. Left out when not measured.
      - `creator_id` string, uuid, nullable, required: The creator's id at the top level (the same as creator.id); null when not known.
      - `unit_id` string, uuid, nullable, required
      - `stage_id` 'planned' | 'offered' | 'creator_confirmed' | 'scheduled' | 'live' | 'proof_attached' | 'cancelled', required: Where this post stands; the same stage model as a campaign's lines_by_stage. Branch on this, never on stage.
      - `stage` string, required: The label for stage_id: Planned, Offered, Creator confirmed, Scheduled, Live, Proof attached, Cancelled.
      - `channel` string, required
      - `channel_label` string, required
      - `format` string, nullable, required
      - `deliverable` string, required
      - `scheduled_date` string, date, required
      - `price_cents` integer, required: The all-in price of this line. Never split into parts.
      - `vetting` string, nullable, required
      - `reason` string, nullable, required
      - `expected_views_low` integer, nullable, required
      - `expected_views_high` integer, nullable, required
      - `on_sale` boolean, required
    - `payment_options` PaymentOption[]
      - `rail` 'checkout' | 'invoice', required
      - `label` string, required
      - `clears` string, required
    - `agent_payment` 'checkout' | 'team_invoice'
    - `brand_terms` object
      - `current_version` string, nullable, required: The current brand terms version id (a date).
      - `current_number` integer, nullable, required: The number the app shows for that version (version 3).
      - `url` string, nullable, required
      - `accepted_for_this_key` string, nullable, required
      - `accepted_number_for_this_key` integer, nullable, required
    - `mandate` Mandate, nullable
      - `key_id` string, uuid
      - `version` integer
      - `max_plan_cents` integer
      - `monthly_cap_cents` integer
      - `channels` string[]
      - `may_approve` boolean
      - `may_exceed_budget` boolean: Retired, always false. No key and no person can approve a plan over its campaign's budget: approve_plan refuses it with over_budget until a creator is removed or the budget is raised.
      - `expires_at` string, date-time
      - `updated_at` string, date-time
      - `spent_this_month_cents` integer
      - `terms_version` string, nullable: The brand terms a person accepted for this key. Null on a mandate that may not sign (the terms govern signing) and on the workspace's own agent key.
      - `terms_accepted_at` string, date-time, nullable
      - `terms_accepted_email` string, nullable
    - `policy` object: allowed: whether this key's mandate lets it sign now; refusal: the reason and numbers when not (over_budget when the plan is over the campaign's budget, which no mandate can approve: remove a creator or have the brand raise the budget).
      - `allowed` boolean
      - `refusal` object, nullable
    - `blocked_lines` string[]
    - `over_budget` object, nullable: The plan over its campaign's budget: budget_cents, total_cents and over_by_cents, at current prices when a creator's price rose past the budget (the plan then keeps its earlier prices, so total_cents alone reads as within it). Null within the budget, with no budget, or once signed. Approval refuses it until a creator is removed or the budget is raised.
      - `budget_cents` integer
      - `total_cents` integer
      - `over_by_cents` integer
    - `order` object, nullable
    - `valid_while` 'terms_fingerprint_unchanged'

## Other responses

- `400`: A parameter is not valid: details.problems names each field and what it accepts.
- `401`: No API key, an unknown one, or a revoked one.
- `403`: The key lacks the scope, or the mandate refuses (mandate_refused, with the limit and how much over).
- `404`: No plan, line or creator of yours has that id.
- `409`: conflict (with a reason), idempotency_conflict, or in_progress (Retry-After).
- `422`: unprocessable: the body is readable but a value cannot be used (a blank or too long name, a campaign that cannot be planned yet); details.problems names each field when there are several.
- `429`: Too many requests for this key in the current minute (60); Retry-After and RateLimit-Reset say when to try again. On POST /campaigns and on POST /plans/current/lines when it starts a plan: the brand's 3 new campaigns in 24 hours are used (details.reason daily_campaign_limit); details.resets_at and Retry-After say when the limit reopens, a rolling 24 hours from the first of them, not a calendar day; archiving or deleting a campaign does not give its slot back. On GET /creators and GET /creators/{id}: a creator read limit is used (details.reason per_minute, daily_lists, daily_reveals, daily_seen or locked). daily_seen is your workspace's one pool of 300 creator views a day (300 different creators shown or opened since midnight UTC), shared by the app and every API key of the brand: details.scope workspace and details.limit 300; a creator already seen today, in the app or through any key, still opens and costs nothing. The other reasons are this key's own limit (details.scope key) or the brand's ceiling over all its keys (details.scope api).
- `500`: internal: the request failed on our side. Retry it with the same Idempotency-Key.
- `503`: unavailable: writes are not available right now (for an approval: payments are not open yet).

---

[API](https://skmtc.dev/distribution/apis/distribution-api.md) · [All operations](https://skmtc.dev/distribution/apis/distribution-api/llms.txt) · [OpenAPI document](https://skmtc.dev/distribution/apis/distribution-api/revisions/5ca6ca44a7c0?raw)
