Docs
Campaigns

Replace a draft's sequence

PUThttps://api.norreach.app/v1/campaigns/{id}/sequence

Replaces the steps of a draft campaign (a live one is cloned to change — 409). Every AI caller a step names must be this workspace's.

campaigns:writeRole must allow: campaigns.editOperation id: replace_campaign_sequence
PUT/campaigns/{id}/sequence
curl -X PUT "https://api.norreach.app/v1/campaigns/{id}/sequence" \
  -H "Authorization: Bearer $NORREACH_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "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?"
    }
  ]
}'
Replace a draft's sequence
{
  "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

Authorizationstringheaderrequired

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

Path parameters

idstring (uuid)pathrequired

An id (UUID).

Headers

Idempotency-Keystringheader

Any unique string (≤ 255). The same key with the same request returns the first answer for 24 hours.

Body

application/json

stepsobject[]required
Show child attributes
steps[].channelenumrequired
One of:emailsmswhatsappcalllinkedin
steps[].wait_hoursnumberrequired

Hours to wait before this step (after the previous one).

steps[].templatestring

The words. Email: "Subject: <line>\n\n<body>". WhatsApp: the approved template's name. Merge tags like {{first_name}} are filled per contact.

steps[].li_kindenum

LinkedIn steps: what the step does.

One of:connectmessagelike_post
steps[].agent_idstring (uuid)

AI-call steps: which of the workspace's AI callers places the call.

steps[].retry_if_no_answerobject

AI-call steps: retry an unanswered call.

Show child attributes
steps[].retry_if_no_answer.enabledbooleanrequired

Dial again when nobody answered or the line was busy.

steps[].retry_if_no_answer.attemptsintegerrequired
steps[].retry_if_no_answer.gap_minutesintegerrequired
steps[].retry_if_no_answer.on_voicemailboolean

Also dial again when a voicemail answered.

steps[].ai_draftedboolean
steps[].coachingstring

Notes for Nora about this step.

steps[].wa_languagestring

WhatsApp steps: the approved template's language code (e.g. en_US).

steps[].wa_paramsstring[]

WhatsApp steps: the values for {{1}}…{{n}}, in order (text or merge tags).

steps[].branchobject

Wait for a signal after this step; without it the contact jumps ahead.

Show child attributes
steps[].branch.onenum

The signal that takes the branch path.

One of:acceptedrepliedopenedclicked
steps[].branch.timeout_hoursnumberrequired
steps[].branch.timeout_tointegerrequired

The step (0-based index) a contact jumps to when the signal doesn't arrive in time.

Response

200application/json— Replace a draft's sequence

idstring (uuid)required

An id (UUID).

object"campaign"required
namestringrequired
statusstringrequired

draft, active, paused, completed, archived or needs_review.

kindenumrequired
One of:broadcastsequencemultichannel
channelsstring[]required

The channel mix of the sequence, in order.

step_countintegerrequired
brand_idstring (uuid) | nullrequired
schedule_atstring | nullrequired
created_atstringrequired

RFC 3339 UTC timestamp with milliseconds.

stepsobject[]required

The sequence (the step shape above).

rulesobject[]required
settingsobjectrequired

The campaign's own settings override.

statsobjectrequired

The campaign's numbers right now.

Show child attributes
stats.enrolledintegerrequired
stats.in_progressintegerrequired
stats.pausedintegerrequired
stats.completedintegerrequired
stats.repliedintegerrequired
stats.meetings_bookedintegerrequired
stats.sentintegerrequired
stats.deliveredintegerrequired
stats.openedintegerrequired
stats.clickedintegerrequired

Errors

Errors are application/problem+json with a stable code.

StatusCodeMeaning
400bad_requestThe request couldn't be read.
400unknown_fieldThe request has a field this operation doesn't know.
401unauthorizedA valid API key is required.
403insufficient_scopeThis API key doesn't have the scope this operation needs.
403feature_not_enabledThe API isn't switched on for this workspace.
403role_forbiddenThe key's creator's role can't do this.
404not_foundThat doesn't exist.
409idempotency_in_progressA request with this Idempotency-Key is still running.
409conflictThat conflicts with the current state.
413too_largeThe request body is too large.
422validation_failedThe request has invalid fields.
422idempotency_mismatchThis Idempotency-Key was used with a different request.
429rate_limitedToo many requests. Slow down and try again.
500server_errorSomething went wrong on our side.
503unavailableThe API isn't available right now. Try again shortly.