GTM Campaigns
GTM Campaigns are graph8’s AI-Studio campaign objects: a brief, a sequence (ordered step references), and a step catalog (the actual step definitions). Each campaign owns a set of generated documents (campaign brief, sequence doc, ad copy, landing page copy, etc.).
These endpoints are the org-scoped flavor used by Studio, MCP, and Claude Desktop GTM mode. The same surface is also exposed under /repos/{repo_id}/campaigns for spine-installed CLI users; see the CLI reference for that variant.
| Endpoint | Method | Description |
|---|---|---|
/campaigns | GET | List campaigns |
/campaigns | POST | Create a campaign |
/campaigns/ideas | GET | List saved campaign ideas |
/campaigns/{id} | GET | Get a campaign with completed documents |
/campaigns/{id} | PATCH | Update campaign content fields |
/campaigns/{id}/documents | GET | List documents for a campaign |
/campaigns/{id}/documents | POST | Create a document |
/campaigns/{id}/documents/{doc_id} | GET | Get full document content |
/campaigns/{id}/documents/{doc_id} | PUT | Update document content |
/campaigns/{id}/documents/{doc_id} | DELETE | Delete a document |
/campaigns/{id}/sequence | GET | Get sequence + step catalog docs |
/campaigns/{id}/sequence | PUT | Replace sequence step ordering |
/campaigns/{id}/sequence/steps | POST | Append a step |
/campaigns/{id}/sequence/steps/{step_id} | PUT | Update a step definition |
/campaigns/{id}/sequence/steps/{step_id} | DELETE | Remove a step |
/campaigns/{id}/launch | POST | Queue campaign launch |
For machine-readable schemas see the interactive API docs.
List Campaigns
GET /campaigns
Returns all non-archived campaigns for the org, paginated.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
page | integer | 1 | Page number |
limit | integer | 50 | 1-200 |
Example
curl "https://be.graph8.com/api/v1/campaigns?limit=10" \ -H "Authorization: Bearer $API_KEY"Response
{ "data": [ { "id": "cmp_abc", "name": "Q2 Outbound - VP Marketing", "slug": "q2-outbound-vp-marketing", "status": "draft", "category": "Outbound", "goal": "Book discovery calls", "target_persona": "VP Marketing at mid-market SaaS", "created_at": "2026-04-01T10:00:00Z" } ]}Create Campaign
POST /campaigns
Create a new campaign. Slug is auto-generated from name.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Campaign name |
category | string | No (default Outbound) | Campaign category |
brief | string | No | Campaign brief |
core_concept | string | No | One-line concept |
primary_hook | string | No | Primary hook text |
target_persona | string | No | Target persona |
goal | string | No | Campaign goal |
Example
curl -X POST "https://be.graph8.com/api/v1/campaigns" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Q2 Outbound - VP Marketing", "category": "Outbound", "primary_hook": "Cut SDR ramp time in half", "goal": "Book discovery calls" }'Response
{ "data": { "id": "cmp_abc", "name": "Q2 Outbound - VP Marketing", "slug": "q2-outbound-vp-marketing", "status": "draft", "category": "Outbound" }}List Campaign Ideas
GET /campaigns/ideas
Returns saved/AI-generated campaign ideas (pre-conversion). Use these to seed POST /campaigns.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
favorited | boolean | - | Filter to favorited ideas only |
limit | integer | 50 | 1-200 |
Response
{ "data": [ { "id": "idea_001", "rank": 1, "campaign_name": "Sales Velocity Diagnostic", "campaign_category": "Outbound", "campaign_focus": "Mid-market RevOps", "core_concept": "...", "why_this_works": "...", "primary_hook": "Find your stuck deals in 5 minutes", "secondary_hooks": ["..."], "target_channels": ["email", "linkedin"], "required_assets": ["landing page", "demo video"], "execution_complexity": "medium", "expected_impact": "high", "proof_points": ["..."], "framework_inspiration": "Challenger", "source": "studio_generated", "favorited": true, "converted_to_campaign": false, "converted_campaign_id": null, "created_at": "2026-04-01T10:00:00Z", "updated_at": "2026-04-01T10:00:00Z" } ]}Get Campaign
GET /campaigns/{campaign_id}
Returns campaign details plus a thumbnail list of completed documents.
Example
curl "https://be.graph8.com/api/v1/campaigns/cmp_abc" \ -H "Authorization: Bearer $API_KEY"Response
{ "data": { "id": "cmp_abc", "name": "Q2 Outbound - VP Marketing", "slug": "q2-outbound-vp-marketing", "status": "draft", "category": "Outbound", "goal": "Book discovery calls", "target_persona": "VP Marketing at mid-market SaaS", "brief": "...", "core_concept": "...", "primary_hook": "Cut SDR ramp time in half", "channels": ["email", "linkedin"], "documents": [ {"id": "doc_001", "name": "Campaign Brief", "type": "campaign_brief"}, {"id": "doc_002", "name": "Sequence", "type": "sequence"} ], "created_at": "2026-04-01T10:00:00Z", "updated_at": "2026-04-10T14:30:00Z" }}Errors
| Status | Meaning |
|---|---|
404 | Campaign not found |
Update Campaign
PATCH /campaigns/{campaign_id}
Update content fields on a campaign.
Request Body
| Field | Type | Description |
|---|---|---|
name | string | Campaign name |
brief | string | Campaign brief |
core_concept | string | Core concept |
primary_hook | string | Primary hook text |
target_persona | string | Target persona |
category | string | Campaign category |
goal | string | Campaign goal |
Errors
| Status | Meaning |
|---|---|
400 | No fields provided |
404 | Campaign not found |
List Campaign Documents
GET /campaigns/{campaign_id}/documents
Returns metadata for every document associated with the campaign (no content payload).
Response
{ "data": { "campaign_id": "cmp_abc", "documents": [ { "id": "doc_001", "campaign_id": "cmp_abc", "display_name": "Campaign Brief", "name": "Campaign Brief", "file_type": "campaign_brief", "type": "campaign_brief", "folder_path": "/brief", "status": "completed", "version": 2, "current_version": 2, "created_at": "2026-04-01T10:00:00Z", "updated_at": "2026-04-05T11:00:00Z" } ], "count": 1 }}Get Campaign Document
GET /campaigns/{campaign_id}/documents/{document_id}
Returns full document content.
Example
curl "https://be.graph8.com/api/v1/campaigns/cmp_abc/documents/doc_001" \ -H "Authorization: Bearer $API_KEY"Response
{ "data": { "id": "doc_001", "campaign_id": "cmp_abc", "name": "Campaign Brief", "type": "campaign_brief", "status": "completed", "version": 2, "content": "# Campaign Brief\n\nGoal: ...", "created_at": "2026-04-01T10:00:00Z", "updated_at": "2026-04-05T11:00:00Z" }}Create Campaign Document
POST /campaigns/{campaign_id}/documents
Create a new document attached to the campaign.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
file_type | string | Yes | Document type (campaign_brief, sequence, step_catalog, etc.) |
display_name | string | Yes | Human-readable name |
content | string | No | Document content (default "") |
folder_path | string | No | Optional folder path |
status | string | No (default draft) | Document status |
Response 201 Created
Returns the same document shape as GET .../documents/{document_id}.
Update Campaign Document
PUT /campaigns/{campaign_id}/documents/{document_id}
Update content. Bumps version and current_version by 1, marks completed=true and generating=false in metadata.
Request Body
| Field | Type | Description |
|---|---|---|
content | string | New content (defaults to existing if empty) |
status | string | Optional status override |
structured_data | object | Optional structured payload stored in metadata |
Errors
| Status | Meaning |
|---|---|
404 | Document not found (or its parent campaign is archived) |
Delete Campaign Document
DELETE /campaigns/{campaign_id}/documents/{document_id}
Hard delete. Returns 404 if the document or its parent campaign is missing.
Response
{ "data": { "status": "deleted", "document_id": "doc_001" } }Get Sequence
GET /campaigns/{campaign_id}/sequence
Returns both the sequence document (ordered list of step references) and the step catalog document (step definitions keyed by step_id).
Response
{ "data": { "campaign_id": "cmp_abc", "sequence": { "steps": [ {"step_id": "step_1", "day": 0, "condition": null, "stop_on_reply": true}, {"step_id": "step_2", "day": 3, "condition": "no_reply", "stop_on_reply": true} ] }, "step_catalog": { "steps": { "step_1": {"name": "Intro email", "channel": "email", "mode": "send", ...}, "step_2": {"name": "LinkedIn follow-up", "channel": "linkedin", "mode": "send", ...} } }, "sequence_doc_id": "doc_seq_001", "catalog_doc_id": "doc_cat_001" }}Errors
| Status | Meaning |
|---|---|
404 | Campaign not found |
Update Sequence
PUT /campaigns/{campaign_id}/sequence
Replace the ordered list of step references. Each step_id must exist in the step catalog.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
steps | object[] | Yes | Ordered step references |
Each step item:
| Field | Type | Description |
|---|---|---|
step_id | string | Step ID (must exist in catalog) |
day | integer | Day offset from sequence start |
condition | string | Optional condition (no_reply, etc.) |
stop_on_reply | boolean | Default true |
Errors
| Status | Meaning |
|---|---|
400 | step_id not in catalog |
404 | Sequence document not found |
Create Sequence Step
POST /campaigns/{campaign_id}/sequence/steps
Create a new step and append it to the sequence in one call. The endpoint mints a fresh step_id (step_N).
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Step name |
channel | string | Yes | email, linkedin, sms, phone, etc. |
mode | string | Yes | send, manual, etc. |
day | integer | Yes | Day offset for sequence placement |
platform | string | No | Sub-channel platform |
angle_id | string | No (default angle_1) | Messaging angle reference |
cta_type | string | No (default soft_ask) | CTA type |
personalization_level | string | No (default medium) | Level of personalization |
constraints | object | No | Constraint dict |
condition | string | No | Sequence condition |
stop_on_reply | boolean | No (default true) | Stop the contact’s enrollment on reply |
Response 201 Created
{ "data": { "status": "created", "step_id": "step_3", "step": { "step_id": "step_3", "name": "Final follow-up", "channel": "email", "mode": "send", "platform": null, "angle_id": "angle_1", "cta_type": "soft_ask", "personalization_level": "medium", "constraints": {} }, "day": 7 }}Update Sequence Step
PUT /campaigns/{campaign_id}/sequence/steps/{step_id}
Update a step definition in the catalog and (optionally) its placement in the sequence. All fields are optional - send only what you want to change.
Request Body
| Field | Type | Description |
|---|---|---|
name | string | Step name |
channel | string | Channel |
mode | string | Mode |
platform | string | Platform |
angle_id | string | Messaging angle |
cta_type | string | CTA type |
personalization_level | string | Level |
constraints | object | Constraint dict |
do_not_send_rules | string[] | Rules that should suppress sending |
day | integer | Sequence placement (updates the sequence doc, not the catalog) |
condition | string | Sequence placement condition |
stop_on_reply | boolean | Sequence placement override |
day, condition, and stop_on_reply modify the sequence document; everything else modifies the catalog entry.
Errors
| Status | Meaning |
|---|---|
404 | Step or step catalog not found |
Delete Sequence Step
DELETE /campaigns/{campaign_id}/sequence/steps/{step_id}
Remove a step from the catalog and any references in the sequence.
Response
{ "data": { "status": "deleted", "step_id": "step_3" } }Errors
| Status | Meaning |
|---|---|
404 | Step not found |
Launch Campaign
POST /campaigns/{campaign_id}/launch
Warning: queues real outreach to real contacts on linked channels. The endpoint returns immediately with launch_queued status; use the spine CLI (g8 watch) or your own polling against /sequences/{id} to monitor progress.
Example
curl -X POST "https://be.graph8.com/api/v1/campaigns/cmp_abc/launch" \ -H "Authorization: Bearer $API_KEY"Response
{ "data": { "status": "launch_queued", "campaign_id": "cmp_abc", "message": "Campaign launch has been queued. Use g8 watch to monitor progress." }}