# Save a campaign's Warm-up template

`PATCH https://api.tryscouty.com/v1/campaigns/{campaignId}/warmup/template`

Saves the Warm-up template for one campaign and the steps it compiles to. Nothing is sent to creators until the flow is published on the dashboard. A campaign built from custom steps is refused: switch it to Warm-up on the dashboard first.

- Scope: `campaigns:write`
- Kind: write
- Safe to retry with the same idempotency key: yes
- MCP tool: `scouty_save_warmup_template`

## Authorization

Send `Authorization: Bearer $SCOUTY_TOKEN`. The token needs the `campaigns:write` scope.

## Path parameters

| Name | Type | Required | Description |
|---|---|---|---|
| `campaignId` | `integer` | Required | The campaign's id. |

## Headers

| Name | Type | Required | Description |
|---|---|---|---|
| `Idempotency-Key` | `string` | Optional | The same key the body's idempotencyKey carries. Send one or the other, or both with the same value. |

## Body parameters

| Name | Type | Required | Description |
|---|---|---|---|
| `idempotencyKey` | `string` | Required | A key you choose for this call, 8 to 128 characters of letters, digits, dot, underscore, colon or hyphen. Sending the same key with the same arguments replays the first answer and changes nothing, so a retry after a timeout is safe. |
| `template` | `object` | Required | The whole Warm-up template: managerName, discordChannelName, platforms, username { startsWith, contains, endsWith }, usernameExamples, launchpointUrl, launchpointProgramId, routine { instructions }, posts (the two warm-up videos: what to film, minSeconds, maxSeconds, exampleUrl, dueHours), review { account, routine, post1, post2 }, readyBy ({ days } or { date }), firstPost ({ daysAfterReady } or { date }). |

## Request

### cURL

```bash
curl -s -X PATCH https://api.tryscouty.com/v1/campaigns/42/warmup/template \
  -H "Authorization: Bearer $SCOUTY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"idempotencyKey":"warmup-template-42","template":{"managerName":"Sam Rivera","discordChannelName":"","platforms":["tiktok"],"username":{"startsWith":"acme.","contains":"","endsWith":""},"usernameExamples":"acme.sam","launchpointUrl":"https://example.com/launchpoint/join","launchpointProgramId":"","routine":{"instructions":"Scroll the For You page for 20 minutes a day for 3 days."},"posts":[{"brief":"A 20 second unboxing.","minSeconds":15,"maxSeconds":30,"exampleUrl":"","dueHours":48},{"brief":"A day with the bottle.","minSeconds":15,"maxSeconds":45,"exampleUrl":"","dueHours":72}],"review":{"account":false,"routine":false,"post1":false,"post2":true},"readyBy":{"days":7},"firstPost":{"daysAfterReady":1}}}'
```

### JavaScript

```js
const response = await fetch("https://api.tryscouty.com/v1/campaigns/42/warmup/template", {
  method: "PATCH",
  headers: {
    Authorization: `Bearer ${process.env.SCOUTY_TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    idempotencyKey: "warmup-template-42",
    template: {
      managerName: "Sam Rivera",
      discordChannelName: "",
      platforms: [
        "tiktok"
      ],
      username: {
        startsWith: "acme.",
        contains: "",
        endsWith: ""
      },
      usernameExamples: "acme.sam",
      launchpointUrl: "https://example.com/launchpoint/join",
      launchpointProgramId: "",
      routine: {
        instructions: "Scroll the For You page for 20 minutes a day for 3 days."
      },
      posts: [
        {
          brief: "A 20 second unboxing.",
          minSeconds: 15,
          maxSeconds: 30,
          exampleUrl: "",
          dueHours: 48
        },
        {
          brief: "A day with the bottle.",
          minSeconds: 15,
          maxSeconds: 45,
          exampleUrl: "",
          dueHours: 72
        }
      ],
      review: {
        account: false,
        routine: false,
        post1: false,
        post2: true
      },
      readyBy: {
        days: 7
      },
      firstPost: {
        daysAfterReady: 1
      }
    }
  }),
});
console.log(await response.json());
```

### Python

```python
import os

import requests

token = os.environ["SCOUTY_TOKEN"]
response = requests.patch(
    "https://api.tryscouty.com/v1/campaigns/42/warmup/template",
    headers={"Authorization": f"Bearer {token}"},
    json={
        "idempotencyKey": "warmup-template-42",
        "template": {"managerName": "Sam Rivera", "discordChannelName": "", "platforms": ["tiktok"], "username": {"startsWith": "acme.", "contains": "", "endsWith": ""}, "usernameExamples": "acme.sam", "launchpointUrl": "https://example.com/launchpoint/join", "launchpointProgramId": "", "routine": {"instructions": "Scroll the For You page for 20 minutes a day for 3 days."}, "posts": [{"brief": "A 20 second unboxing.", "minSeconds": 15, "maxSeconds": 30, "exampleUrl": "", "dueHours": 48}, {"brief": "A day with the bottle.", "minSeconds": 15, "maxSeconds": 45, "exampleUrl": "", "dueHours": 72}], "review": {"account": False, "routine": False, "post1": False, "post2": True}, "readyBy": {"days": 7}, "firstPost": {"daysAfterReady": 1}},
    },
)
print(response.json())
```

## Responses

### 200 OK

```json
{
  "saved": true,
  "steps": 7
}
```

### 400 Bad Request

```json
{
  "error": {
    "code": "invalid_request",
    "message": "Those arguments are not the shape this capability takes: idempotencyKey."
  }
}
```

### 401 Unauthorized

```json
{
  "error": {
    "code": "missing_token",
    "message": "This API needs an Authorization header carrying a bearer token."
  }
}
```

### 403 Forbidden

```json
{
  "error": {
    "code": "insufficient_scope",
    "message": "This token does not carry the campaigns:write scope. Ask the team for one that does."
  }
}
```

### 404 Not Found

```json
{
  "error": {
    "code": "not_found",
    "message": "No such campaign."
  }
}
```

### 409 Conflict

```json
{
  "error": {
    "code": "idempotency_conflict",
    "message": "That idempotency key was already used for a different request. Use a new key."
  }
}
```

### 429 Too Many Requests

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests. Please wait for the current minute to end."
  }
}
```

## Errors

| Status | `error.code` | Meaning |
|---|---|---|
| 400 Bad Request | `invalid_request` | The request was not the shape this capability takes, or its body was not JSON. |
| 401 Unauthorized | `missing_token`, `invalid_token`, `token_revoked`, `token_expired`, `token_orphaned` | No token, or one that is not recognized, revoked or expired. |
| 403 Forbidden | `insufficient_scope`, `wrong_principal` | The token does not carry the scope this capability needs. |
| 404 Not Found | `not_found` | No such route, or no such campaign for this client. |
| 409 Conflict | `idempotency_conflict` | That idempotency key was already used for a different request. Nothing was changed. |
| 429 Too Many Requests | `rate_limited` | Too many requests this minute. Retry-After says how long is left. |

Full reference: https://docs.tryscouty.com/reference
