Lynkist Developers

Campaigns

Schedule and run bulk template sends — full CRUD, audience selection, lifecycle, and analytics.

A campaign is a bulk template send: pick a template, define an audience (groups + sources), set a variable mapping that fills the template's {{n}} placeholders from each contact's fields, and dispatch. The Public Campaigns API mirrors the dashboard's lifecycle endpoints, with two deliberate omissions:

  • No individual contact IDs in the audience. API users put contacts into a group first, then add the group to the campaign. This removes a class of unknown-number / opt-out edge cases from the API surface.
  • No test send or CSV upload. Those are dashboard-only QA affordances for humans.
ScopeEndpoints
campaigns:readList, get, audience preview, field coverage, errors summary / drilldown
campaigns:writeCreate, set audience, render preview, refresh metrics
campaigns:sendSend now, schedule, pause, resume, stop

Base path: /api/v1/public/campaigns

State machine

        ┌────────┐  set_audience       ┌────────────┐  schedule   ┌─────────────┐
        │ DRAFT  │ ──────────────────▶ │  DRAFT     │ ─────────▶  │  SCHEDULED  │
        └────┬───┘                     │ (audience  │             └─────┬───────┘
             │ send_now                │  attached) │                   │
             ▼                         └─────┬──────┘                   │ (scheduled_at arrives)
        ┌──────────┐                         │ send_now                 │
        │ RUNNING  │ ◀───────────────────────┘ ◀─────────────────────── │
        └────┬─────┘                                                    
             │ pause                       resume                      
             ├──────────────────▶ PAUSED ──────────▶ RUNNING            
             │ stop                                                     
             ├──────────────────▶ STOPPED (terminal)                    
             │ all recipients done                                      
             ├──────────────────▶ COMPLETED (terminal)                  
             │ fatal error                                              
             └──────────────────▶ FAILED (terminal)                     

Terminal statuses: COMPLETED, STOPPED, FAILED. Each transition emits a corresponding campaign.* webhook event.

Campaign object

FieldTypeNotes
idUUID
namestring
descriptionstring | null
statusstringOne of DRAFT, SCHEDULED, RUNNING, PAUSED, COMPLETED, STOPPED, FAILED.
template_idUUID
template_namestring | null
template_categorystring | nullMARKETING, UTILITY, AUTHENTICATION.
template_languagestring | null
variable_mappingobject | nullRecipe for filling template variables. Loosely typed — see Variable mapping.
scheduled_atISO-8601 | nullUTC.
started_atISO-8601 | null
completed_atISO-8601 | null
total_recipientsintegerMaterialised once the campaign starts.
statsobject{ total, sent, delivered, read, failed, pending }.
created_atISO-8601 | null
updated_atISO-8601 | null

GET /campaigns/{id} additionally returns an audiences array (group + source rows).

Variable mapping

The variable_mapping describes how to fill each template variable from contact fields. Its exact shape is documented in the internal Campaigns README — the Public API accepts it as a loose object so the underlying mapping grammar can evolve without breaking external clients. The campaign engine validates it on the way in; if it's wrong you get a 422. Build the mapping in the dashboard once, then read it back via GET /campaigns/{id} and reuse it.

CRUD

GET /campaigns — List campaigns

Newest first. Not paginated today.

curl https://api.lynkist.io/api/v1/public/campaigns \
  -H "Authorization: Bearer $LYNKIST_API_KEY"

200 OK{ "data": [campaign, …] }.

POST /campaigns — Create a campaign

curl -X POST https://api.lynkist.io/api/v1/public/campaigns \
  -H "Authorization: Bearer $LYNKIST_API_KEY" \
  -H "Idempotency-Key: 7c4d2e1a-…" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "May launch — India",
    "description": "Marketing blast for May product launch",
    "template_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
    "batch_size": 50,
    "variable_mapping": { /* see Variable mapping */ },
    "audience": {
      "groups":  ["g-1234…"],
      "sources": ["s-5678…"]
    },
    "scheduled_at": "2026-06-02T09:30:00Z"
  }'
FieldRequiredNotes
nameYes1-120 characters.
descriptionNo≤ 500 characters.
template_idYesLynkist's template UUID or the Meta template id.
batch_sizeNo (50)1-1000. How many recipients are queued per Celery batch.
variable_mappingNoRequired for templates with variables. Otherwise omit.
audienceNoGroups + sources only. Can be set later.
scheduled_atNoIf present, campaign starts in SCHEDULED; otherwise DRAFT.

201 Created — returns the campaign object.

Emits campaign.created (and campaign.scheduled if scheduled_at is set).

GET /campaigns/{id} — Get a campaign

Returns the campaign object plus the audiences array.

curl https://api.lynkist.io/api/v1/public/campaigns/cb-… \
  -H "Authorization: Bearer $LYNKIST_API_KEY"

Audience

PUT /campaigns/{id}/audience — Set audience

Replaces the audience. Allowed only while DRAFT or PAUSED.

curl -X PUT https://api.lynkist.io/api/v1/public/campaigns/cb-…/audience \
  -H "Authorization: Bearer $LYNKIST_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "groups": ["g-1234…"], "sources": ["s-5678…"] }'

GET /campaigns/{id}/audience/preview — Preview resolved audience

Resolves the groups + sources into the actual recipient set without materialising recipient rows in the database. Useful for "how many will this hit?" before send.

curl https://api.lynkist.io/api/v1/public/campaigns/cb-…/audience/preview \
  -H "Authorization: Bearer $LYNKIST_API_KEY"

200 OK

{
  "total": 1247,
  "sample": [
    { "id": "c-1…", "first_name": "Anurag", "phone": "+91…" },
    { "id": "c-2…", "first_name": "Lara",   "phone": "+44…" }
  ]
}

POST /campaigns/{id}/audience/field-coverage — Field coverage report

For each field name you supply, returns how many of the resolved audience have it populated. Use this to catch "30% of recipients have no email" before send.

curl -X POST https://api.lynkist.io/api/v1/public/campaigns/cb-…/audience/field-coverage \
  -H "Authorization: Bearer $LYNKIST_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "fields": ["first_name", "email", "custom_fields.order_id"] }'

200 OK

{
  "total_recipients": 1247,
  "coverage": {
    "first_name":              { "present": 1247, "missing": 0 },
    "email":                   { "present":  812, "missing": 435 },
    "custom_fields.order_id":  { "present":  118, "missing": 1129 }
  }
}

Preview render

POST /campaigns/{id}/preview — Render a template preview

Resolves the campaign's variable_mapping against one sample contact and returns the rendered template. If sample_contact_id is omitted, the first contact in the audience is used.

curl -X POST https://api.lynkist.io/api/v1/public/campaigns/cb-…/preview \
  -H "Authorization: Bearer $LYNKIST_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "sample_contact_id": "c-1…" }'

200 OK

{
  "campaign_id": "cb-…",
  "sample_contact_id": "c-1…",
  "resolved_components": [
    { "type": "BODY", "text": "Hi Anurag, your order ORD-1042 has shipped." }
  ]
}

Lifecycle

POST /campaigns/{id}/send — Send now

Materialises the audience (if not already) and starts dispatch immediately. Transitions DRAFT or SCHEDULEDRUNNING.

curl -X POST https://api.lynkist.io/api/v1/public/campaigns/cb-…/send \
  -H "Authorization: Bearer $LYNKIST_API_KEY"

Emits campaign.started.

POST /campaigns/{id}/schedule — Schedule

Moves to SCHEDULED with a UTC start time.

curl -X POST https://api.lynkist.io/api/v1/public/campaigns/cb-…/schedule \
  -H "Authorization: Bearer $LYNKIST_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "scheduled_at": "2026-06-02T09:30:00Z" }'

Emits campaign.scheduled.

POST /campaigns/{id}/pause — Pause

Pauses a RUNNING or SCHEDULED campaign. In-flight messages drain; new ones do not enqueue. Emits campaign.paused.

POST /campaigns/{id}/resume — Resume

Resumes a PAUSED campaign. Emits campaign.resumed.

POST /campaigns/{id}/stop — Stop

Permanently terminates a campaign. Cannot be resumed. Recipients not yet dispatched stay in pending forever (they remain queryable via the errors endpoints). Emits campaign.stopped.

Analytics

POST /campaigns/{id}/refresh-metrics — Recompute stats

Force-recomputes stats from the recipient rows. Stats normally update in the background; use this if you want a fresh snapshot synchronously (e.g. before showing a dashboard widget).

curl -X POST https://api.lynkist.io/api/v1/public/campaigns/cb-…/refresh-metrics \
  -H "Authorization: Bearer $LYNKIST_API_KEY"

GET /campaigns/{id}/errors — Errors summary

Buckets failed recipients by Meta error code. Use it to see the shape of your failures at a glance — "300 hit code 131056 (marketing cap), 12 hit 470 (no template)…"

curl https://api.lynkist.io/api/v1/public/campaigns/cb-…/errors \
  -H "Authorization: Bearer $LYNKIST_API_KEY"

200 OK

{
  "total_failed": 312,
  "by_code": [
    {
      "code": "131056",
      "title": "Marketing template rate cap reached",
      "fix": "Wait for the rolling 24h window to reset, or move to a different template.",
      "count": 300,
      "sample_message": "Meta: this number has hit the marketing message cap…"
    },
    {
      "code": "470",
      "title": "Template not found / disabled",
      "fix": "Verify the template is still approved and not in a disabled state.",
      "count": 12,
      "sample_message": "Template 'order_confirmation' in en_US is disabled."
    }
  ]
}

GET /campaigns/{id}/errors/{error_code} — Errors drill-down

Lists individual recipients that failed with a specific code. Capped at limit=200 per call.

curl "https://api.lynkist.io/api/v1/public/campaigns/cb-…/errors/131056?limit=50" \
  -H "Authorization: Bearer $LYNKIST_API_KEY"

200 OK

{
  "error_code": "131056",
  "recipients": [
    {
      "id": "r-…",
      "phone_number": "+91…",
      "contact_id": "c-…",
      "status": "failed",
      "message_id": "wamid.HBgM…",
      "error": "Meta: marketing template rate cap reached"
    }
  ]
}

Errors

StatusWhen
400Bad transition (e.g. pause on a terminal campaign), bad audience input
401Missing / invalid API key
403Key lacks the required scope (campaigns:read / write / send)
404Campaign not found
409Concurrent transition lost the race (rare; retry after re-reading the state)
422variable_mapping shape invalid

Webhooks

EventWhen
campaign.createdAfter POST /campaigns
campaign.scheduledAfter POST /campaigns/{id}/schedule
campaign.startedAt dispatch start (manual or scheduled)
campaign.pausedAfter POST /campaigns/{id}/pause
campaign.resumedAfter POST /campaigns/{id}/resume
campaign.stoppedAfter POST /campaigns/{id}/stop
campaign.completedAll recipients processed
campaign.failedFatal error during dispatch (template disabled mid-flight, WABA disconnected, etc.)
Campaigns — Lynkist Developers | Lynkist