---
title: "Create a campaign"
method: POST
path: "/v1/campaigns"
tags: ["Campaigns"]
---

# Create a campaign

`POST /v1/campaigns`

## Request

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

Create a draft campaign: one or more steps (the first email, then its follow-ups), each a template with `{{variables}}` sent to every recipient. A draft opens in the app like one created there, to be reviewed and launched, or edited with `PATCH /v1/campaigns/:campaignId`.

Variables: For people: `{{fullName}}`, `{{firstName}}`, `{{lastName}}`, `{{jobTitle}}`, `{{description}}`, `{{urls}}`, `{{companies.name}}`, `{{companies.urls}}`. For companies: `{{name}}`, `{{description}}`, `{{fundingRaised}}`, `{{lastFundingDate}}`, `{{foundationYear}}`, `{{industry}}`, `{{employeeRange}}`. Array fields use their first value. A text or numeric custom field of the campaign's group is `{{customFieldValues.grp_<uuid>.<field name>}}`. A variable without a value for a recipient is removed. Write `\{{` for literal braces.

End a body with `<p>{{signature}}</p>` to sign it with the sender's signature; a body without it is sent unsigned.

With `target: "review"`, the campaign is created, then the messages of every recipient are generated from the steps: it answers 202 with the campaign `processing`, and the campaign becomes `in_review` within seconds, to poll with `GET /v1/campaigns/:campaignId`. If generation fails, it is `in_review` with an `error`. A campaign that can't be reviewed, such as one whose first step has no subject, is not created.

Recipients the request names but that you can't access, and recipients without an email address, are listed in `warnings`. `target: "launch"` and `messages` are not supported yet.<br/><Warning>This endpoint is currently in open beta. The API surface may change as we iterate based on feedback.</Warning>

## 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
  - `target` 'draft' | 'review' | 'launch', required: What to do once the campaign is created. `draft` stores it, to be finished in the app or edited with `PATCH /v1/campaigns/:campaignId`. `review` and `launch` are not supported yet.
  - `groupId` string: The group to create the campaign in. Its text and numeric custom fields can be used as variables. Leave it out to send to a list of contacts.
  - `sender` union: The sender of every step. Set either a root `sender` for every step, or a `sender` on every step, not both. Without any, every step is sent from the sender you last used.
    - object
      - `id` string, required
      - `email` string
      - `displayName` string, nullable
    - object
      - `id` unknown
      - `email` string, email, required
      - `displayName` string, nullable
  - `stopOnReply` boolean: Whether the remaining steps stop for a recipient who replied. Defaults to true.
  - `scheduledAt` string, date-time: When to send, with `launch`. Not supported yet.
  - `recipients` union, required: Who receives the campaign: a list of contacts, or the contacts of its group. Between 1 and 5,000 recipients, all people or all companies.
    - object: An explicit list of recipients.
      - `type` 'contacts', required
      - `ids` string[], required: The people (`per_…`) or companies (`com_…`) to send to, all of the same type. Merged contacts resolve to the contact they were merged into. With `groupId`, they must belong to the group.
    - object: The people or companies of the campaign's group, optionally filtered. Requires `groupId`.
      - `type` 'group', required
      - `entityType` 'person' | 'company', required: Whether the recipients are people or companies.
      - `filter` object: Only send to the contacts of the group matching this filter, written as for `GET /v1/people` and `GET /v1/companies`.
      - `combinator` 'and' | 'or': How the conditions of `filter` combine: `and` (default) or `or`.
      - `search` string: Only send to the contacts of the group matching this search.
      - `excludeIds` string[]: Contacts of the group not to send to.
  - `steps` CampaignStepInput[], required: 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).
    - `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
  - `messages` CampaignPersonalizedMessageInput[]: Personalized messages, with `review` and `launch`. Not supported yet.
    - `recipient` object, required: The person or company the message is for.
      - `id` string, required
    - `step` integer, required: The position of the step the message belongs to.
    - `subject` string
    - `body` string, required
    - `bodyFormat` 'html' | 'text'
    - `cc` string[]: Email addresses copied on the message. At most 5 distinct addresses; duplicates are removed.
    - `bcc` string[]: Email addresses blind-copied on the message. At most 5 distinct addresses; duplicates are removed.

## Response `201`

The campaign created, 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/post.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)
