---
title: "Launch Campaign"
method: POST
path: "/campaigns/{campaign_id}/launch"
tags: ["GTM Campaigns"]
---

# Launch Campaign

`POST /campaigns/{campaign_id}/launch`

## Request

- Base URL: `https://be.graph8.com/api/v1`
- URL: `https://be.graph8.com/api/v1/campaigns/{campaign_id}/launch`
- Auth: HTTP bearer

Launch a campaign through the canonical Sequencer V2 workflow.

This does two things end-to-end:

1. **Push** the campaign to the sequencer. The bridge loads the campaign's
   generated documents (email/LinkedIn/phone/SMS copy), transforms them
   into sequencer steps, and creates a new ``SeqSequence`` row with the
   campaign content attached. A ``CampaignSequenceLink`` row links the two.
2. **Run** the sequence through the same V2 workflow used by Sequencer.
   The response reports ``live`` only after synchronous initialization has
   completed, or ``scheduling`` with job details for background work.

Idempotency: re-launching a campaign that already has an active sequence
returns 409 unless ``force`` is implied via a future query-param. This is
consistent with the UI's bridge/push behaviour.

Pre-flight is mandatory. The deprecated ``validate`` parameter is accepted
only as ``true``; ``false`` is rejected before any sequence is created.

WARNING: this begins sending real outreach to real contacts.

## Path parameters

- `campaign_id` string, required: Campaign ID

## Query parameters

- `validate` boolean: Deprecated compatibility parameter. Readiness validation is always enforced; false is rejected before any sequence is created.

## Headers

- `Authorization` string, nullable
- `X-Target-Org-Id` string, nullable

## Request body

- CampaignLaunchRequest: Optional body for ``POST /campaigns/{id}/launch`` — all fields optional. Callers that POST with no body (or an empty {}) use the first active email mailbox for the organization. Callers that want explicit control pass ``sender_mailbox_ids``. Launch fails before sequence creation when no supported active sender is available. The MCP `g8_gtm_launch_campaign` wrapper passes ``sender_mailbox_ids`` when Daniel's agent already picked a sender; otherwise the auto-pick fallback keeps the one-shot launch flow working with no extra round-trip.
  - `sender_mailbox_ids` integer[], nullable: Explicit list of mailbox IDs (from GET /mailboxes) to attach as sequence channels before launch. Each ID must reference a connection_status='active', non-archived mailbox in this org. When omitted, the bridge auto-picks the first active email mailbox (ORDER BY id ASC). If no supported sender is available, launch fails before sequence creation.
  - `disable_auto_pick` boolean: Deprecated safety escape hatch. Launch now rejects True because a consequential action cannot proceed without an active sender.
  - `textual_agent_name` string, nullable: Voice-service agent identifier (from GET /agents) used for email / social steps. Stamped onto seqsequences.textual_agent_name. Pass the same value as `voice_agent_name` to use one agent across all channels (the common case — the unified Agent row already bundles persona + voice + phone + the attached appointment event).
  - `voice_agent_name` string, nullable: Voice-service agent identifier (from GET /agents) used for phone / SMS steps. Stamped onto seqsequences.voice_agent_name. When you want a single agent for all channels, set both this and `textual_agent_name` to the same value.
  - `schedule_id` string, nullable: Send-window schedule UUID (from GET /schedules). Stamped onto seqsequences.schedule_id. When omitted the sequencer falls back to its org-default send window.
  - `appointment_id` integer, nullable: Calendar event-type ID (from GET /event-types) to attach as the booking link on the sequence. Stamped onto seqsequences.appointment_id. When omitted no booking link is attached and any sequence step referencing `{{booking_link}}` will render the org-default link (or empty).

## Response `200`

Successful Response

- ApiResponseDict
  - `data` object, required
  - `pagination` PaginationMeta
    - `page` integer, required: Current page number (1-indexed)
    - `limit` integer, required: Items per page
    - `total` integer, required: Total number of items
    - `has_next` boolean, required: Whether there are more pages
    - `next_cursor` string, nullable: Opaque cursor for the next page; null when there are no more items. Pass it back as the `cursor` query param to fetch the following page without computing page numbers.
    - `total_available` integer, nullable: Search endpoints only: how many records really match, when known. Can be larger than `total`, which is capped at the 10,000 results that can be paged. For company search this is a distinct-company estimate. Null when unknown; absent outside search.
    - `total_is_lower_bound` boolean, nullable: Search endpoints only: true when more records match than `total` shows (the result set is larger than the 10,000 results that can be paged). Absent outside search.
    - `notes` string[], nullable: Search endpoints only: plain-language notes about the result, e.g. that more records match than can be paged, or that a reversed `between` range was put in order.

## Other responses

- `422`: Validation Error

---

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