> ## Documentation Index
> Fetch the complete documentation index at: https://docs.goyappr.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Update a campaign

Partial update of a campaign's configuration. Only the keys you send are changed; everything else is left alone. Editing a `running` campaign is allowed and takes effect at the next evaluation, roughly within a minute — you do not have to pause first.

Terminal campaigns (`completed`, `stopped`, `archived`) can no longer be edited and return 400.

Required scope: `campaigns:manage`.

## Path parameters

| Parameter | Type | Notes                                                |
| --------- | ---- | ---------------------------------------------------- |
| `id`      | uuid | Campaign ID. Must belong to the API key's workspace. |

## Writable fields

The same allowlist as `POST /campaigns`, all optional here. Ranges, defaults and guidance are on the create page.

| Field                                                                                                           | Type            | Range / values                                                        |
| --------------------------------------------------------------------------------------------------------------- | --------------- | --------------------------------------------------------------------- |
| `name`                                                                                                          | string          | Non-empty, trimmed. Does not have to be unique.                       |
| `description`                                                                                                   | string          | —                                                                     |
| `agent_id`                                                                                                      | uuid            | An agent in your workspace with a positive `max_call_duration_secs`.  |
| `from_phone_number_id`                                                                                          | uuid            | An active number you own.                                             |
| `regulatory_basis`                                                                                              | string          | `consent`, `existing_customer`, `non_marketing`, `registry_screened`. |
| `stop_disposition_ids`                                                                                          | uuid\[]         | Disposition IDs belonging to your workspace.                          |
| `stop_on_no_answer`, `stop_on_voicemail`, `stop_on_unclassified`, `randomize_retry_time`, `double_dial_enabled` | boolean         | —                                                                     |
| `max_attempts`                                                                                                  | integer         | `1`–`999`                                                             |
| `max_infra_retries`                                                                                             | integer         | `0`–`20`                                                              |
| `retry_no_answer_seconds`                                                                                       | integer         | `30`–`604800`                                                         |
| `retry_completed_seconds`                                                                                       | integer         | `60`–`604800`                                                         |
| `double_dial_gap_seconds`                                                                                       | integer         | `10`–`3600`                                                           |
| `max_calls_per_day`                                                                                             | integer         | `1`–`100000`                                                          |
| `min_seconds_between_calls`                                                                                     | integer         | `0`–`86400`                                                           |
| `max_in_flight`                                                                                                 | integer         | `1`–`8`                                                               |
| `budget_cents`                                                                                                  | integer \| null | Positive integer, or `null` to remove the cap.                        |
| `calling_window`                                                                                                | object          | —                                                                     |
| `starts_at`, `ends_at`                                                                                          | timestamp       | ISO-8601.                                                             |

Unknown keys are **rejected with 400**, and so is every engine-owned field (`status`, `spent_cents`, `last_tick_result`, `total_leads`, …). Status changes go through the transition endpoints — `POST /campaigns/{id}/launch`, `/pause`, `/resume`, `/stop` — and never through `PATCH`.

An empty body, or a body containing no writable field, is a 400 rather than a no-op.

## Editing a live campaign

* **Stop rules apply forward only.** Adding a disposition to `stop_disposition_ids` retires contacts that land it *from now on*; it does not retroactively retire contacts already called. Removing one never resurrects a contact that was already retired.
* **Raising `max_attempts` does not revive `exhausted` contacts.** They stay terminal.
* **Lowering pacing takes effect immediately** — a smaller `max_calls_per_day` than today's `daily_admitted_count` simply stops the campaign for the rest of the day.
* **Changing `agent_id` or `from_phone_number_id`** affects the next call, not calls already in flight. Pointing at an inactive number or an agent with no duration cap will pause the campaign as `paused_config` at the next evaluation.
* **Raising `budget_cents`** on a `paused_budget` campaign does not resume it. Call `POST /campaigns/{id}/resume`.

## Example request

```bash theme={null}
# Slow it down, extend the attempt cap, and add a stop disposition.
curl -X PATCH "https://api.goyappr.com/campaigns/CAMPAIGN_ID" \
  -H "Authorization: Bearer $YAPPR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "max_calls_per_day": 80,
    "min_seconds_between_calls": 90,
    "max_in_flight": 1,
    "max_attempts": 4,
    "stop_disposition_ids": [
      "5c1d9a2e-0000-4b10-9f31-000000000010",
      "5c1d9a2e-0000-4b10-9f31-000000000011",
      "5c1d9a2e-0000-4b10-9f31-000000000012"
    ]
  }'
```

```bash theme={null}
# Remove the budget cap.
curl -X PATCH "https://api.goyappr.com/campaigns/CAMPAIGN_ID" \
  -H "Authorization: Bearer $YAPPR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "budget_cents": null }'
```

## Example response

The complete campaign after the update, same shape as `GET /campaigns/{id}`:

```json theme={null}
{
  "id": "b3f1c0d2-5a44-4f0e-9c11-7a2e8d3f0001",
  "company_id": "fe493f11-0000-0000-0000-000000000001",
  "name": "July renewals",
  "status": "running",
  "max_calls_per_day": 80,
  "min_seconds_between_calls": 90,
  "max_in_flight": 1,
  "max_attempts": 4,
  "stop_disposition_ids": [
    "5c1d9a2e-0000-4b10-9f31-000000000010",
    "5c1d9a2e-0000-4b10-9f31-000000000011",
    "5c1d9a2e-0000-4b10-9f31-000000000012"
  ],
  "stop_dispositions": [
    { "id": "5c1d9a2e-0000-4b10-9f31-000000000010", "label": "Do Not Call" },
    { "id": "5c1d9a2e-0000-4b10-9f31-000000000011", "label": "Not Interested" },
    { "id": "5c1d9a2e-0000-4b10-9f31-000000000012", "label": "Wrong Number" }
  ],
  "updated_at": "2026-07-28T12:04:19.771Z"
}
```

Trimmed for brevity — the real response contains every field listed on `GET /campaigns/{id}`.

## Errors

| HTTP | Code                 | When                                                                                                                                                       |
| ---- | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 400  | —                    | Campaign is `completed`, `stopped` or `archived` and can no longer be edited.                                                                              |
| 400  | —                    | Unknown or read-only field in the body. The message lists every writable field.                                                                            |
| 400  | —                    | No writable field supplied.                                                                                                                                |
| 400  | —                    | `name` present but empty, a numeric field out of range, a boolean field not a boolean, or `budget_cents` not a positive integer.                           |
| 400  | —                    | `regulatory_basis` not one of the four accepted values, `stop_disposition_ids` not an array of IDs from your workspace, or `calling_window` not an object. |
| 400  | —                    | Request body is not valid JSON.                                                                                                                            |
| 401  | `INSUFFICIENT_SCOPE` | API key lacks `campaigns:manage`.                                                                                                                          |
| 404  | —                    | No campaign with that ID in this workspace, or it has been archived.                                                                                       |
