---
title: "Update a campaign"
method: PATCH
path: "/email/campaigns/{id}"
tags: ["Campaigns"]
---

# Update a campaign

`PATCH /email/campaigns/{id}`

## Request

- Base URL: `https://www.unitpost.com/api/v1`
- URL: `https://www.unitpost.com/api/v1/email/campaigns/{id}`
- Auth: HTTP bearer

Edit a campaign while it's still in `draft` (content edits after that return `409`). Beyond content and targeting, you can: send `archived` to archive/restore it; set `variable_fallbacks` (fill-only per-variable defaults) and/or `excluded_contact_ids` to resolve a send blocked by missing variables — the API equivalent of the dashboard's pre-send fix; set `topic_id` to scope it to a subscription topic (or `null` to clear); and set `open_tracking`/`click_tracking` (tri-state; `null` inherits the template/domain default). See what's blocking a send with [Validate a campaign](#get-email-campaigns-id-validate). Requires the `campaigns:write` capability.

## Request body

- CampaignUpdate
  - `archived` boolean
  - `topic_id` string, nullable
  - `open_tracking` boolean, nullable
  - `click_tracking` boolean, nullable
  - `variable_fallbacks` object
  - `excluded_contact_ids` string[]

## Response `200`

The updated campaign.

- Campaign
  - `object` 'campaign', required
  - `id` string, required
  - `name` string, required
  - `template_id` string, required
  - `segment_id` string, required
  - `topic_id` string, nullable, required
  - `from` string, required
  - `subject` string, nullable, required
  - `preview_text` string, nullable, required
  - `open_tracking` boolean, nullable, required
  - `click_tracking` boolean, nullable, required
  - `status` 'draft' | 'scheduled' | 'sending' | 'paused' | 'sent' | 'canceled' | 'failed', required
  - `scheduled_at` string, nullable, required
  - `variable_fallbacks` object, required
  - `excluded_contact_ids` string[], required
  - `total_recipients` number, required
  - `sent_count` number, required
  - `failed_count` number, required
  - `pause_reason` object, nullable, required: Why the campaign is paused, or null when it isn't. `code` is `user` for a pause you made; `hard_bounce_rate` / `complaint_rate` when the per-campaign quality check paused it (with `metric`, `value`, `threshold`, `sample` and `window`); `quality_review` when the delivery reviewer did. `resume`: `self_serve` (resume after cleaning the list), `review_required` (a repeat pause; Resume answers 409 `review_required`), `account_review` (staff are reviewing the account), `resume_slowly` (a provider is limiting the sender).
    - `code` 'user' | 'hard_bounce_rate' | 'complaint_rate' | 'quality_review' | 'provider_limit' | 'failure_streak' | 'sender_unavailable' | 'template_unavailable' | 'billing' | 'not_drained' | 'sending_restricted' | 'workspace_suspended', required
    - `message` string, nullable, required
    - `metric` 'hard_bounce_rate' | 'complaint_rate', nullable, required
    - `value` number, nullable, required
    - `threshold` number, nullable, required
    - `sample` object, nullable, required
      - `events` number, required
      - `of` number, required
    - `window` object, nullable, required
      - `since` string, nullable, required
    - `decided_by` 'floor' | 'unit', nullable, required
    - `resume` 'self_serve' | 'review_required' | 'account_review' | 'resume_slowly', nullable, required
    - `paused_at` string, nullable, required
  - `created_at` string, required
  - `updated_at` string, required

## Other responses

- `401`: Missing or invalid API key.
- `404`: Campaign not found.
- `409`: Only `draft` campaigns can be edited.
- `422`: Validation error.

---

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