Skip to content

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.

EndpointMethodDescription
/campaignsGETList campaigns
/campaignsPOSTCreate a campaign
/campaigns/ideasGETList saved campaign ideas
/campaigns/{id}GETGet a campaign with completed documents
/campaigns/{id}PATCHUpdate campaign content fields
/campaigns/{id}/documentsGETList documents for a campaign
/campaigns/{id}/documentsPOSTCreate a document
/campaigns/{id}/documents/{doc_id}GETGet full document content
/campaigns/{id}/documents/{doc_id}PUTUpdate document content
/campaigns/{id}/documents/{doc_id}DELETEDelete a document
/campaigns/{id}/sequenceGETGet sequence + step catalog docs
/campaigns/{id}/sequencePUTReplace sequence step ordering
/campaigns/{id}/sequence/stepsPOSTAppend a step
/campaigns/{id}/sequence/steps/{step_id}PUTUpdate a step definition
/campaigns/{id}/sequence/steps/{step_id}DELETERemove a step
/campaigns/{id}/launchPOSTQueue 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

ParameterTypeDefaultDescription
pageinteger1Page number
limitinteger501-200

Example

Terminal window
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

FieldTypeRequiredDescription
namestringYesCampaign name
categorystringNo (default Outbound)Campaign category
briefstringNoCampaign brief
core_conceptstringNoOne-line concept
primary_hookstringNoPrimary hook text
target_personastringNoTarget persona
goalstringNoCampaign goal

Example

Terminal window
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

ParameterTypeDefaultDescription
favoritedboolean-Filter to favorited ideas only
limitinteger501-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

Terminal window
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

StatusMeaning
404Campaign not found

Update Campaign

PATCH /campaigns/{campaign_id}

Update content fields on a campaign.

Request Body

FieldTypeDescription
namestringCampaign name
briefstringCampaign brief
core_conceptstringCore concept
primary_hookstringPrimary hook text
target_personastringTarget persona
categorystringCampaign category
goalstringCampaign goal

Errors

StatusMeaning
400No fields provided
404Campaign 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

Terminal window
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

FieldTypeRequiredDescription
file_typestringYesDocument type (campaign_brief, sequence, step_catalog, etc.)
display_namestringYesHuman-readable name
contentstringNoDocument content (default "")
folder_pathstringNoOptional folder path
statusstringNo (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

FieldTypeDescription
contentstringNew content (defaults to existing if empty)
statusstringOptional status override
structured_dataobjectOptional structured payload stored in metadata

Errors

StatusMeaning
404Document 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

StatusMeaning
404Campaign 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

FieldTypeRequiredDescription
stepsobject[]YesOrdered step references

Each step item:

FieldTypeDescription
step_idstringStep ID (must exist in catalog)
dayintegerDay offset from sequence start
conditionstringOptional condition (no_reply, etc.)
stop_on_replybooleanDefault true

Errors

StatusMeaning
400step_id not in catalog
404Sequence 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

FieldTypeRequiredDescription
namestringYesStep name
channelstringYesemail, linkedin, sms, phone, etc.
modestringYessend, manual, etc.
dayintegerYesDay offset for sequence placement
platformstringNoSub-channel platform
angle_idstringNo (default angle_1)Messaging angle reference
cta_typestringNo (default soft_ask)CTA type
personalization_levelstringNo (default medium)Level of personalization
constraintsobjectNoConstraint dict
conditionstringNoSequence condition
stop_on_replybooleanNo (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

FieldTypeDescription
namestringStep name
channelstringChannel
modestringMode
platformstringPlatform
angle_idstringMessaging angle
cta_typestringCTA type
personalization_levelstringLevel
constraintsobjectConstraint dict
do_not_send_rulesstring[]Rules that should suppress sending
dayintegerSequence placement (updates the sequence doc, not the catalog)
conditionstringSequence placement condition
stop_on_replybooleanSequence placement override

day, condition, and stop_on_reply modify the sequence document; everything else modifies the catalog entry.

Errors

StatusMeaning
404Step 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

StatusMeaning
404Step 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

Terminal window
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."
}
}