The Campaigns API exposes the full campaign lifecycle: create, configure, enroll contacts, launch, pause, resume, analyze, and archive. Every endpoint respects workspace isolation and the deliverability brain's resume gate. This article covers the endpoints with example requests, the interactions with contacts and sequences, and the common programmatic patterns.
Base URL and Authentication
https://getnimbusos.com/api/v1/campaigns/
Bearer token authentication. JWT or API key with the campaigns:read and campaigns:write scopes.
Campaign Object
{
"id": "camp_abc123",
"workspace_id": "ws_xyz789",
"name": "Q2 Cold Outreach - Tier A RevOps",
"status": "active",
"campaign_type": "cold_outreach",
"platform": "reachinbox",
"sequence_id": "seq_def456",
"sending_account_ids": ["ea_001", "ea_002", "ea_003"],
"min_health_score": 70,
"daily_send_limit": 500,
"max_new_leads_per_day": 50,
"smart_time_gaps": true,
"send_window_start": 9,
"send_window_end": 17,
"send_days": [1, 2, 3, 4, 5],
"open_tracking_enabled": true,
"link_tracking_enabled": true,
"stop_on_reply": true,
"stop_on_domain_reply": false,
"stop_on_unsubscribe": true,
"contact_filter": {
"segment_ids": ["seg_hot_abc"],
"icp_tier": ["A"],
"grade": ["A", "B"]
},
"metrics": {
"total_sent": 1250,
"delivered": 1238,
"opened": 622,
"clicked": 89,
"replied": 47,
"bounced": 12,
"unsubscribed": 3,
"spam_complaints": 0,
"open_rate": 0.503,
"reply_rate": 0.038,
"bounce_rate": 0.010
},
"started_at": "2026-04-10T09:00:00Z",
"created_at": "2026-04-09T16:00:00Z",
"updated_at": "2026-04-23T14:00:00Z"
}
List Campaigns
GET /api/v1/campaigns/?status=active,paused&page_size=50
Query parameters:
status: draft, active, paused, completed, archivedcampaign_type: cold_outreach, nurture, re_engagement, warmupplatform: reachinbox, smartlead, instantly, lemlist, nativeclient_id: agency-only filtercreated_after,updated_after
Response has the same cursor pagination shape as Contacts API.
Get Campaign
GET /api/v1/campaigns/camp_abc123/
Returns the full campaign object with metrics.
Create Campaign
POST /api/v1/campaigns/
Content-Type: application/json
{
"name": "Q2 Cold Outreach - Tier A RevOps",
"campaign_type": "cold_outreach",
"platform": "reachinbox",
"sequence_id": "seq_def456",
"sending_account_ids": ["ea_001", "ea_002", "ea_003"],
"min_health_score": 70,
"daily_send_limit": 500,
"max_new_leads_per_day": 50,
"smart_time_gaps": true,
"send_window_start": 9,
"send_window_end": 17,
"stop_on_reply": true,
"contact_filter": {
"segment_ids": ["seg_hot_abc"]
}
}
Creation returns the campaign in draft status with a 201. Campaigns are not live until explicitly launched.
Validation runs on create:
sequence_idmust exist in the workspacesending_account_idsmust exist and be activecontact_filtermust resolve to at least 1 contact
Validation errors return 400 with field-level detail.
Update Campaign
PATCH /api/v1/campaigns/camp_abc123/
Content-Type: application/json
{
"daily_send_limit": 750,
"max_new_leads_per_day": 75
}
Some fields can be updated after launch (limits, filters). Others are immutable once active (campaign_type, platform). Attempting to update an immutable field returns 422.
Launch Campaign
POST /api/v1/campaigns/camp_abc123/launch/
Runs the resume gate check for cold_outreach campaigns. Returns:
- 200 OK: campaign moved to
activestatus. - 422 Gate Blocked: the resume gate failed. Response body includes the specific reason (e.g., "fleet has only 14 stage 3+ inboxes; 20 required").
When the gate blocks, the campaign remains in draft. You can fix the underlying condition and re-launch.
Pause Campaign
POST /api/v1/campaigns/camp_abc123/pause/
Content-Type: application/json
{
"reason": "reviewing_copy"
}
Halts sends immediately. In-flight sends already queued for the current batch complete. New enrollments stop.
Resume Campaign
POST /api/v1/campaigns/camp_abc123/resume/
Re-runs the resume gate check (cold_outreach only). On success, campaign moves back to active. Pending sends re-queue at their original schedule; sends past their schedule by over 24 hours are dropped.
Archive Campaign
POST /api/v1/campaigns/camp_abc123/archive/
Moves the campaign to archived status. No further operations allowed except read. Archived campaigns are excluded from the default list view but are retained indefinitely for analytics and audit.
Enrollment Endpoints
Get current enrollments
GET /api/v1/campaigns/camp_abc123/enrollments/?status=active&page_size=100
Returns contact IDs and per-contact sequence state.
Manually enroll contacts
POST /api/v1/campaigns/camp_abc123/enroll/
Content-Type: application/json
{
"contact_ids": ["cont_abc", "cont_def"]
}
Enrolls the contacts even if they do not match the campaign's filter. Useful for testing or specific overrides.
Remove contacts
POST /api/v1/campaigns/camp_abc123/remove-contacts/
Content-Type: application/json
{
"contact_ids": ["cont_abc"],
"stop_pending_sends": true
}
Removes contacts from the campaign. If stop_pending_sends=true, any queued sends for those contacts are canceled.
Analytics Endpoints
Campaign summary
GET /api/v1/campaigns/camp_abc123/analytics/summary/
Returns the metrics object with a time series breakdown.
Step-level breakdown
GET /api/v1/campaigns/camp_abc123/analytics/steps/
Returns per-step metrics for the campaign's sequence.
Reply intent distribution
GET /api/v1/campaigns/camp_abc123/analytics/reply-intents/
Returns counts per reply intent category.
Bounce breakdown
GET /api/v1/campaigns/camp_abc123/analytics/bounces/
Returns bounce classification counts and sample SMTP codes.
Export
GET /api/v1/campaigns/camp_abc123/export/?format=csv&include=events
Generates an export with the full send log. Async for campaigns over 10k sends; returns a job ID to poll.
Webhook Events
Campaign events emitted via webhook:
campaign.createdcampaign.launchedcampaign.pausedcampaign.resumedcampaign.auto_paused(from deliverability brain)campaign.completedcampaign.archived
Subscribe through the Webhooks API.
Common Patterns
Pattern 1: Create and launch in one script
import requests
headers = {"Authorization": "Bearer nmbapi_..."}
# Create draft
resp = requests.post(
"https://getnimbusos.com/api/v1/campaigns/",
headers=headers,
json={
"name": "Automated Q3 Cold",
"campaign_type": "cold_outreach",
"platform": "reachinbox",
"sequence_id": "seq_abc",
"sending_account_ids": ["ea_001", "ea_002"],
"contact_filter": {"segment_ids": ["seg_hot"]},
"max_new_leads_per_day": 25,
},
)
camp_id = resp.json()["id"]
# Launch
launch = requests.post(
f"https://getnimbusos.com/api/v1/campaigns/{camp_id}/launch/",
headers=headers,
)
if launch.status_code == 422:
print("Gate blocked:", launch.json())
Pattern 2: Daily metrics pull
Scheduled job pulls metrics for every active campaign and writes to an internal data warehouse for custom dashboards.
GET /api/v1/campaigns/?status=active
Then for each, GET /api/v1/campaigns/:id/analytics/summary/.
Pattern 3: Auto-pause on external signal
External system detects a quality issue. Calls POST /api/v1/campaigns/:id/pause/ with a reason. Team is notified via Slack. Investigation happens before manual resume.
Pattern 4: Sequence rotation
Script creates a new campaign weekly with the latest sequence template, enrolls the next batch of contacts from a segment, launches after the gate clears. This is how agencies run continuous outreach pipelines without manual campaign creation.
Rate Limits
Campaign endpoints respect the standard API rate limits (see Rate Limits article). Launch and resume are counted as multiple operations internally; expect to be able to launch several campaigns per minute, not dozens.
Bulk analytics pulls for many campaigns in parallel should stay under 10 concurrent requests to avoid rate limit blowback.
Error Responses
Common campaign-specific errors:
- 422
gate_blocked: resume gate failed on launch or resume - 409
invalid_transition: trying to pause a draft, launch an active, etc. - 422
empty_filter: contact filter resolves to zero contacts - 422
inactive_inbox: a selected sending account is paused
Response body includes actionable detail.
Troubleshooting
"Campaign was created but has zero enrollments after 1 hour"
Contact filter is empty. Check contact_filter resolves to contacts. Run the search endpoint with the same filter to verify.
"Launch returned 422 and I cannot figure out why"
The 422 response includes a blocking_reasons array. Check each reason. Most common: fleet stage count, bounce rate, or no eligible sending inbox.
"Analytics summary is empty even though the campaign is live"
First sends take 5 to 15 minutes to process. If longer, check the campaign's enrollment count. Zero enrollments means the contact filter is producing no matches.
"Pause did not stop queued sends"
Pause halts enrollment and new sends but does not cancel sends that are already in the platform-level send queue. Use remove-contacts with stop_pending_sends=true to cancel specific pending sends.
Frequently Asked Questions
Can I clone a campaign via API?
Yes. POST /api/v1/campaigns/:id/clone/ creates a new draft with the same configuration. Name appends "(copy)" by default. Clone does not carry over enrollments.
How do I get the real-time send log?
GET /api/v1/campaigns/:id/sends/ with cursor pagination. For streaming, use the webhook events (email.sent, etc.) rather than polling this endpoint.
Can I create a campaign from a template via API?
Not directly. Create from scratch with the sequence_id of a template-derived sequence. Full template-to-campaign shortcut is a UI-only feature.
Is there a rate limit on campaign creation?
10 campaign creations per minute per workspace is the soft limit. For higher volume, contact support.
What to Read Next
Useful next pages after this one: Contacts API for the contact endpoints that campaigns reference, Webhooks API for the event stream from campaigns, and Rate Limits for throughput management.