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

# Update a campaign

`PATCH /v1/campaigns/{campaignId}`

## Request

- Base URL: `https://api.folk.app`
- URL: `https://api.folk.app/v1/campaigns/{campaignId}`
- Auth: HTTP bearer

Edit a draft campaign: its sender, its steps or whether it stops on reply. `steps` replaces every step, so send them all, as read from `GET /v1/campaigns/:campaignId`. Recipients and whether they are people or companies can't change.<br/><Warning>This endpoint is currently in open beta. The API surface may change as we iterate based on feedback.</Warning>

## Path parameters

- `campaignId` string, required

## Headers

- `Idempotency-Key` string: A unique key, such as a UUID, that makes retrying this request safe: a retry with the same key and body returns the original response without running the operation again. Keys are kept for 24 hours after the request completes.

## Request body

- object
  - `sender` union
    - object: The sender of every step. Set either a root `sender` for every step, or a `sender` on every step, not both. A step whose sender changes gets the new sender's signature, or none when they have none.
      - `id` string, required
      - `email` string
      - `displayName` string, nullable
    - object: The sender of every step. Set either a root `sender` for every step, or a `sender` on every step, not both. A step whose sender changes gets the new sender's signature, or none when they have none.
      - `id` unknown
      - `email` string, email, required
      - `displayName` string, nullable
  - `steps` CampaignStepInput[]: The steps of the campaign: the first email, then its follow-ups. More than one step needs sequences on your plan, and a sender that supports them (not Gmail). They replace the current steps: steps match by position, extra steps are added and missing ones removed. A body ending with `{{signature}}` keeps the step's signature as stored, edited or not.
    - `sender` union
      - object: The sender of this step. Set either a root `sender` for every step, or a `sender` on every step, not both.
        - `id` string, required
        - `email` string
        - `displayName` string, nullable
      - object: The sender of this step. Set either a root `sender` for every step, or a `sender` on every step, not both.
        - `id` unknown
        - `email` string, email, required
        - `displayName` string, nullable
    - `subject` string: The subject template, in plain text with `{{variables}}`. A follow-up sent in the same thread has no subject of its own.
    - `body` string: The body template, in HTML (or plain text with `bodyFormat: "text"`), with `{{variables}}`. End it with `<p>{{signature}}</p>` to sign it with the sender's signature, and don't write the signature yourself: a body without `{{signature}}` is sent unsigned. An unsubscribe link, for SendGrid senders only, is `<a href="{{unsubscribeLink}}">`. HTML outside the email editor's format (styles, tables, unknown tags) is simplified, with warnings.
    - `bodyFormat` 'html' | 'text': How `body` is written. `html` (default) is read as HTML. `text` is plain text: a blank line starts a new paragraph, each further blank line adds an empty paragraph, a single newline is a line break, and `<` and `&` are literal characters. Bodies always read back as HTML.
    - `cc` string[]: Email addresses copied on every message. At most 5 distinct addresses; duplicates are removed.
    - `bcc` string[]: Email addresses blind-copied on every message. At most 5 distinct addresses; duplicates are removed.
    - `delayDays` integer: Follow-ups only: days to wait after the previous step, from 1 to 365. Defaults to 1. The first step only takes 0, as read.
    - `sameThread` boolean: Follow-ups only: send the step as a reply in the first step's thread, with its subject. Defaults to false.
    - `position` unknown
    - `status` unknown
    - `messageCount` unknown
    - `sentCount` unknown
    - `notSentCount` unknown
  - `stopOnReply` boolean: Whether the remaining steps stop for a recipient who replied.

## Response `200`

The campaign, with its steps.

- object
  - `data` CMPCampaign, required: A campaign: one or more emails, each a step, sent to a list of people or companies.
    - `id` string, required
    - `name` string, required: The name of the campaign.
    - `status` 'draft' | 'processing' | 'in_review' | 'scheduled' | 'sending' | 'finished' | 'cancelled' | 'failed', required: The status of the campaign. One of: draft, processing, in_review, scheduled, sending, finished, cancelled, failed. `processing` means messages are being generated: poll until `in_review`.
    - `groupId` string, nullable, required: The group the campaign was created from, or null for a campaign created from a selection of contacts, or whose group was deleted since.
    - `entityType` 'person' | 'company', required: Whether the recipients are people or companies.
    - `steps` CampaignStep[], required: The steps of the campaign, by position. A campaign that sends a single email has one step.
      - `position` integer, required: The 1-based position of the step in the campaign. Steps are addressed by position.
      - `sender` object, nullable, required: Who the step is sent from. Each step has its own sender. `id` is null once the sender was deleted; `email` and `displayName` are then those the step was set up with. Null when the step has no sender.
        - `id` string, nullable, required: The ID of the sender, or null once it was deleted. `email` and `displayName` are then those the email was set up with.
        - `email` string, required: The email address the email is sent from.
        - `displayName` string, nullable, required: The name the email is sent under.
      - `subject` string, required: The subject template, in plain text with `{{variables}}`. Literal `{{` is written `\{{`.
      - `body` string, required: The body template, in HTML with `{{variables}}`. Always HTML, whatever format it was written in. A signed body ends with `<p>{{signature}}</p>`; an unsubscribe link is `<a href="{{unsubscribeLink}}">`.
      - `cc` string[], required: Email addresses copied on every message.
      - `bcc` string[], required: Email addresses blind-copied on every message.
      - `delayDays` integer, required: Days to wait after the previous step before sending this one. 0 for the first step.
      - `sameThread` boolean, required: Whether the step is sent as a reply in the first step's thread, with its subject.
      - `status` 'draft' | 'processing' | 'in_review' | 'scheduled' | 'sending' | 'sent' | 'cancelled' | 'failed', required: The status of the step. One of: draft, processing, in_review, scheduled, sending, sent, cancelled, failed. `scheduled` means the campaign is scheduled or sending, and the step waits for its send date.
      - `messageCount` integer, required: The number of messages generated for this step.
      - `sentCount` integer, required: The number of messages of this step that were sent.
      - `notSentCount` integer, required: The number of messages of this step that were not sent.
    - `stopOnReply` boolean, required: Whether the remaining steps stop for a recipient who replied.
    - `recipientCount` integer, required: The number of recipients of the campaign.
    - `createdBy` string, required: The ID of the user who created the campaign.
    - `createdAt` string, date-time, required
    - `updatedAt` string, date-time, nullable, required
    - `scheduledAt` string, date-time, nullable, required
    - `startedAt` string, date-time, nullable, required
    - `finishedAt` string, date-time, nullable, required
    - `error` 'spam_detected' | 'not_enough_credits' | 'send_failed' | 'generation_failed' | 'review_failed' | 'null', nullable, required: Why the campaign failed, when its status is `failed`, or what went wrong while preparing a `draft` or `in_review` campaign, such as `generation_failed` when its messages could not all be generated. One of: spam_detected, not_enough_credits, send_failed, generation_failed, review_failed.
  - `deprecations` string[]

## Other responses

- `400`: The request was unacceptable, often due to missing an invalid parameter.
- `401`: No valid API key provided.
- `402`: The workspace doesn’t have enough credits to perform the request.
- `403`: The API key doesn’t have permissions to perform the request.
- `404`: The requested resource doesn’t exist.
- `409`: A request with the same `Idempotency-Key` is still being processed, or the resource is in a state that does not allow the operation.
- `422`: The request was unacceptable, often due to missing or invalid parameters.
- `429`: Too many requests hit the API too quickly. We recommend an exponential backoff of your requests.
- `500`: Something went wrong on our end.
- `503`: The server is overloaded or down for maintenance.

## Changes

- **2026-11-03** `c6818f3ba60d`: 1 info
  - endpoint added

[Operation history](https://skmtc.dev/folk/apis/folk-external-api/changes/v1/campaigns/:campaignId/patch.md)

---

[API](https://skmtc.dev/folk/apis/folk-external-api.md) - [All operations](https://skmtc.dev/folk/apis/folk-external-api/llms.txt) - [OpenAPI document](https://skmtc.dev/folk/apis/folk-external-api/revisions/c6818f3ba60d?raw)
