Create a draft campaign
https://api.norreach.app/v1/campaignsSaves a new campaign as a draft — nothing is sent until it is launched (POST /campaigns/{id}/launch). The sequence may start empty; a broadcast is exactly one step and never LinkedIn. Every AI caller and brand named must be this workspace's.
curl -X POST "https://api.norreach.app/v1/campaigns" \
-H "Authorization: Bearer $NORREACH_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"name": "Summer nurses — Stockholm",
"kind": "sequence",
"steps": [
{
"channel": "email",
"wait_hours": 0,
"template": "Subject: Nurses for the summer\n\nHi {{first_name}}, …"
},
{
"channel": "sms",
"wait_hours": 48,
"template": "Hi {{first_name}}, did you see my email?"
}
],
"rules": [
{
"name": "Stop on reply",
"when": {
"event": "replied"
},
"then": {
"action": "pause"
}
}
]
}'{
"id": "3c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e6f",
"object": "campaign",
"name": "Summer nurses — Stockholm",
"status": "draft",
"kind": "sequence",
"channels": [
"email",
"sms"
],
"step_count": 2,
"brand_id": null,
"schedule_at": null,
"created_at": "2026-11-04T09:12:33.120Z",
"steps": [
{
"channel": "email",
"wait_hours": 0,
"template": "Subject: Nurses for the summer\n\nHi {{first_name}}, …"
},
{
"channel": "sms",
"wait_hours": 48,
"template": "Hi {{first_name}}, did you see my email?"
}
],
"rules": [
{
"name": "Stop on reply",
"when": {
"event": "replied"
},
"then": {
"action": "pause"
}
}
],
"settings": {
"approve_before_send": "manual"
},
"stats": {
"enrolled": 120,
"in_progress": 80,
"paused": 4,
"completed": 20,
"replied": 14,
"meetings_booked": 2,
"sent": 210,
"delivered": 204,
"opened": 96,
"clicked": 12
}
}Authorizations
Bearer authentication: send Authorization: Bearer <key>, with an API key or an OAuth access token. The key needs the scope campaigns:write. API keys and scopes
Headers
Any unique string (≤ 255). The same key with the same request returns the first answer for 24 hours.
Body
application/json
broadcastsequencemultichannelShow child attributesHide child attributes
emailsmswhatsappcalllinkedinHours to wait before this step (after the previous one).
The words. Email: "Subject: <line>\n\n<body>". WhatsApp: the approved template's name. Merge tags like {{first_name}} are filled per contact.
LinkedIn steps: what the step does.
connectmessagelike_postAI-call steps: which of the workspace's AI callers places the call.
AI-call steps: retry an unanswered call.
Show child attributesHide child attributes
Dial again when nobody answered or the line was busy.
Also dial again when a voicemail answered.
Notes for Nora about this step.
WhatsApp steps: the approved template's language code (e.g. en_US).
WhatsApp steps: the values for {{1}}…{{n}}, in order (text or merge tags).
Wait for a signal after this step; without it the contact jumps ahead.
Show child attributesHide child attributes
The signal that takes the branch path.
acceptedrepliedopenedclickedThe step (0-based index) a contact jumps to when the signal doesn't arrive in time.
Show child attributesHide child attributes
activepausedShow child attributesHide child attributes
repliedopenedclickedconnection_acceptedmeeting_bookedbouncedShow child attributesHide child attributes
ai_callpausecompletebook_meetingCampaign settings (a sparse override of the workspace's defaults).
Show child attributesHide child attributes
manual = every send waits for a person's yes. Setting auto needs the nora:autonomy scope.
automanualNora answers replies on her own. Setting true needs the nora:autonomy scope.
Unsubscribe link settings.
How opting out works.
The opt-out wording.
Re-engagement settings.
Send follow-ups in the same email thread.
new, followups or both.
Pause a contact who is out of office.
Sending window and pacing.
What happens when a contact replies.
New contacts started per day.
Prospects per month.
Stop the sequence for a contact who replies.
Stop the sequence for a contact once an AI call connects.
Place an AI call when a contact shows intent.
Deliverability settings.
Who is skipped at enrollment (recency_days, per_company_cap).
Only send inside the sending window.
Message writing preferences.
A scheduled start (launching then waits until this time).
An id (UUID).
A scheduled start (must be in the future).
Response
201application/json— Create a draft campaign
An id (UUID).
draft, active, paused, completed, archived or needs_review.
broadcastsequencemultichannelThe channel mix of the sequence, in order.
RFC 3339 UTC timestamp with milliseconds.
The sequence (the step shape above).
The campaign's own settings override.
The campaign's numbers right now.
Show child attributesHide child attributes
Errors
Errors are application/problem+json with a stable code.
| Status | Code | Meaning |
|---|---|---|
| 400 | bad_request | The request couldn't be read. |
| 400 | unknown_field | The request has a field this operation doesn't know. |
| 401 | unauthorized | A valid API key is required. |
| 403 | insufficient_scope | This API key doesn't have the scope this operation needs. |
| 403 | feature_not_enabled | The API isn't switched on for this workspace. |
| 403 | role_forbidden | The key's creator's role can't do this. |
| 409 | idempotency_in_progress | A request with this Idempotency-Key is still running. |
| 413 | too_large | The request body is too large. |
| 422 | validation_failed | The request has invalid fields. |
| 422 | idempotency_mismatch | This Idempotency-Key was used with a different request. |
| 429 | rate_limited | Too many requests. Slow down and try again. |
| 500 | server_error | Something went wrong on our side. |
| 503 | unavailable | The API isn't available right now. Try again shortly. |