---
title: "Edit a draft"
method: PATCH
path: "/api/v1/campaigns/{id}"
tags: ["Campaigns"]
---

# Edit a draft

`PATCH /api/v1/campaigns/{id}`

## Request

- Base URL: `https://mailcheer.com`
- URL: `https://mailcheer.com/api/v1/campaigns/{id}`
- Auth: one of:
  - HTTP bearer
  - API key in header `X-Api-Key`

Permission required: `campaigns:write`. Changes a campaign with status `draft`: name, subject, preheader, kind, sender, content (`content` in blocks, or `text`). Nothing is sent. Any other status answers 409 `conflict` with the current status in `details.status`, and nothing is changed: what is scheduled, sending or sent is not rewritten.

## Path parameters

- `id` string, required

## Request body

- CampaignUpdate: The fields of `CampaignRequest`, all optional. Unknown fields are refused (`422`): a misspelled field ignored in silence would let the old subject go to the whole list. `content` and `text` together are refused. New content without `preheader` keeps the draft's.
  - `name` string
  - `subject` string: The subject. It may stay empty on a draft, but the send will refuse it.
  - `preheader` string: Preview shown after the subject in the inbox.
  - `kind` string: Your own classification label. Free text, no imposed value.
  - `from` string: The address of a sender **already registered** in the workspace — not any address on a verified domain, unlike an individual send. Unknown: 404, with the list of available addresses. `GET /api/v1/me` gives them in `senders`.
  - `sender_id` string: The sender's identifier, if you know it (`senders[].id` from `GET /api/v1/me`). Takes precedence over `from`. With neither `from` nor `sender_id`, Mailcheer takes the workspace's default sender; failing that, if a single sender exists, that one. If several exist with no default, the campaign is created **without a sender** and it is the send that will fail: designate one.
  - `text` string: The content in plain text — the form to prefer from code or from an agent. A blank line separates two paragraphs, `# ` at the start of a line makes a heading, `## ` a subheading. Ignored if `content` is provided.
  - `content` object: The content in blocks, the editor's full form. Write this only if you are reusing the content of an existing campaign: to compose, `text` does the same job without your having to fabricate block identifiers.
    - `preheader` string
    - `blocks` object[]: Editor blocks, each with its `id` and its `type` (`heading`, `text`, …).

## Response `200`

The draft, as it is now.

- Campaign
  - `id` string
  - `object` 'campaign'
  - `name` string
  - `subject` string, nullable
  - `kind` string
  - `status` 'draft' | 'scheduled' | 'sending' | 'sent' | 'archived'
  - `from` string, nullable
  - `scheduled_at` string, date-time, nullable
  - `sent_at` string, date-time, nullable
  - `created_at` string, date-time
  - `schedule_error` string, nullable: Why a scheduled send did not go out.
  - `stats` object: A bot is an open or a click within two minutes of delivery (security gateways visit every link as the message arrives; privacy relays preload images), or one from a robot that declares itself in its user agent or comes from an address range its operator publishes (Google, Bing). Privacy relays (Apple Mail, Gmail, Yahoo) are not bots in themselves.
    - `recipients` integer
    - `sent` integer
    - `delivered` integer
    - `opened` integer
    - `clicked` integer
    - `bounced` integer
    - `complained` integer
    - `open_rate` number, nullable: Percentage of delivered messages opened at least once, one decimal — bots included, as most tools count it.
    - `click_rate` number, nullable: Percentage of delivered messages clicked at least once, one decimal — bots included.
    - `human_opened` integer: Messages opened (or clicked) at least once by a person, bots set aside.
    - `human_clicked` integer: Messages clicked at least once by a person, bots set aside.
    - `human_open_rate` number, nullable: `open_rate` without bots: the closest figure to real readings.
    - `human_click_rate` number, nullable: `click_rate` without bots.
    - `opens_tracked` boolean: `false` when every delivered message went out without an open-tracking pixel (the workspace's “Track opens” setting was off): `open_rate` and `human_open_rate` are then `null`. Open figures only count messages that carried the pixel.

## Other responses

- `401`: Key missing, unknown or revoked.
- `403`: The key does not have the requested permission, or the workspace cannot send: `sending_blocked` (suspended, blocked or banned — nothing goes out, through any channel), `commitment_required` (the anti-spam commitment is not accepted), `reputation_blocked` (too many bounces or complaints).
- `404`: The object does not exist in this workspace.
- `409`: Not a draft: nothing was changed.
- `422`: Malformed field, unverified domain, or recipient on the suppression list.

---

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