---
title: "Create or update a campaign (e.g. name='Q2 Outreach'). Omit campaign_id to create; provide it to update. Creation fails"
method: POST
path: "/api/v1/campaigns/upsert"
tags: ["campaigns"]
---

# Create or update a campaign (e.g. name='Q2 Outreach'). Omit campaign_id to create; provide it to update. Creation fails

`POST /api/v1/campaigns/upsert`

## Request

- Base URL: `https://crispy.sh`
- URL: `https://crispy.sh/api/v1/campaigns/upsert`
- Auth: HTTP bearer

## Request body

- object
  - `campaign_id` string: Campaign ID to update. Omit to create a new campaign.
  - `id` string: Optional URL-safe slug for creation (3-50 chars: lowercase a-z, 0-9, hyphens; starts/ends alphanumeric). Auto-derived from name when omitted. Provide to override or to resolve a name collision.
  - `name` string: Campaign name (required for creation; the slug id is derived from it).
  - `kind` 'acquisition' | 'relationship': REQUIRED for creation. 'acquisition' = invite-based outreach to new 1st-degrees (filters out existing connections). 'relationship' = DM-only to existing 1st-degree connections (requires DISTANCE_1).
  - `list_id` string: Existing list ID to use as prospect source (alternative to contacts).
  - `contacts` object[]: Inline ContactRef array for creation (alternative to list_id).
  - `follow_up_sequence` object[]: Post-acceptance (or relationship DM) steps to persist. Replaces stored follow_up_sequence. Pass [] to clear. Each step needs type and delay_seconds; text steps need content.

## Response `200`

Success

## Other responses

- `401`: Missing or expired API key
- `403`: Insufficient key scope or permission denied
- `429`: Rate limit exceeded

---

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