Campaigns
A campaign binds an agent to a list of contacts and a goal. On the voice channel, activating a campaign queues a call for every pending contact and dials them within the campaign’s schedule, retrying people who did not answer. On whatsapp and email, activation either sends an opening message to every contact or only arms the agent to answer people who write in, depending on how the campaign is configured. Campaigns start as drafts, so nothing reaches a customer until you activate one.
The Quickstart creates a first campaign end to end. This page is the full reference: the object, the state machine, how contacts are worked through, and the limits that apply.
How it works
- Create the campaign. It is stored in
draftand nothing is sent. - Add contacts to it, one at a time, in bulk or from a CSV file. See Contacts.
- Activate it. Jelliu runs the channel’s preflight checks, flips the status to
activeand queues the outreach. - Workers process each contact. Before every dial or send they re-read the campaign, so a pause or a channel change takes effect on jobs that are already queued.
- Completion is automatic. A campaign moves to
completedwhen it has nopendingcontacts and no call still in progress. The check runs whenever a contact settles, and a background sweep asks again every 15 minutes.
Status transitions
Only two endpoints change the status: activate and pause. There is no endpoint that completes or archives a campaign.
Two other rules depend on the status:
PATCH /api/campaigns/{campaignId}is refused forcompletedandarchivedcampaigns.DELETE /api/campaigns/{campaignId}is refused foractivecampaigns. Pause first.
archived exists in the status enum, but no API operation moves a campaign into it today. Treat it as a terminal status if you encounter it.
Channels
The agent must serve the campaign’s channel (its channels list). A mismatch is refused on create, on a channel change and again on activation, with 400 VALIDATION_FAILED.
The schedule gates voice dialing only. WhatsApp and email outreach is paced (sends are spaced out per worker) but it is not held to the schedule’s days and hours: activating a WhatsApp or email campaign at 23:00 sends at 23:00.
How voice contacts are worked through
For every queued contact, the dialer checks, in order:
- The campaign is still
activeand still a voice campaign. Otherwise the job ends and the contact stayspending. - The schedule. Outside the window, the dial is postponed to the start of the next enabled window, up to 14 times. A contact that exceeds that, or a schedule with no upcoming window at all, is marked
failed. - The contact is still
pending, and its current phone number is used, not the one it had when the campaign was activated. - The number is dialable. A value that is not E.164, or that the carrier lookup reports as unroutable, marks the contact
invalidwithout spending an attempt. - Consent. If the workspace requires consent before calling, a contact without voice-call consent is marked
invalid. - Claim. The contact is atomically moved to
called, so two workers can never dial the same person. - The call. A contact blocked by compliance rules or the do-not-call list is marked
dnc. See Calls for everything that happens once the call is placed.
Concurrency. A call is placed only if the campaign has fewer live calls than its max_concurrent_calls and the workspace has a free slot under its plan’s concurrent-call limit. When either limit is reached, the dial is not failed: the contact returns to pending and the dial is rescheduled with a backoff of 30 seconds growing to 10 minutes.
Retries. When a call ends without reaching the person (no answer, busy line, or a call that never connected), Jelliu schedules another dial after retry_interval_minutes. max_retry_attempts is the total number of dials per contact in a run, including the first one: with the default of 3, a contact is called at most three times. 0 disables retries. Retries go back through the same checks, so they also respect the schedule. Contacts that are converted, dnc or invalid are never retried.
Campaign context reaches the agent
name, productContext and targetAudience are not just labels. On every campaign call they are passed to the agent as the campaign_name, product_context and target_audience variables, together with the contact’s name and your company name. Text conversations (WhatsApp, email, web chat) with a contact that belongs to a campaign receive the same three variables. Write productContext as the brief you would give a human agent: what is being offered and what the conversation is for.
Paused by Jelliu
Some failures belong to the workspace, not to a contact. When a dial fails because the workspace has no phone number of its own (PHONE_NUMBER_REQUIRED) or because the campaign’s agent is paused (AGENT_PAUSED), Jelliu pauses the campaign, writes the reason to blocked_reason and leaves every remaining contact pending. Fix the cause and activate the campaign again to resume.
blocked_reason is not cleared when the campaign is reactivated. Read it together with status: it only describes the current state while the campaign is paused.
The campaign object
Campaign responses are the stored row, with snake_case field names, even though request bodies use camelCase.
Fields added by the list endpoint
GET /api/campaigns returns a lighter row. It includes id, tenant_id, agent_id, name, status, category, channel, max_concurrent_calls, max_retry_attempts, schedule, blocked_reason, created_at, updated_at and deleted_at, plus these aggregates. It does not include product_context, target_audience, retry_interval_minutes or the WhatsApp and email fields; fetch the campaign by ID for those.
called_contacts and conversion_rate count phone calls only. On whatsapp and email campaigns they stay at 0 even after every contact has been messaged. Use total_contacts and pending_contacts to track progress on those channels.
Schedule
Hours are whole hours: startHour: 9, endHour: 18 allows calls to start from 09:00 up to 17:59. A day that does not appear in days is treated as disabled.
Template variables
WhatsApp templates contain numbered placeholders such as {{1}}. You can fill them two ways, and combine them:
whatsappTemplateVariables: the same value for every contact. Either an array of up to 20 strings, or an object keyed by placeholder number (keys up to 8 characters). Values are up to 500 characters.whatsappTemplateVariableMap: a value resolved per contact, keyed by placeholder number (keys up to 3 characters). Each entry is either a token string, or{ "token": "...", "fallback": "..." }with a fallback of up to 500 characters. Placeholders the map does not mention keep their campaign-wide value.
Email campaigns write the same tokens inline in emailSubject and emailBody, wrapped in double braces, for example Hola {{contact.first_name}}.
Unknown tokens are rejected when you save the campaign. A mapped placeholder number that the template does not have is rejected too.
A contact whose tokens do not resolve, and whose map entry has no fallback, is skipped rather than sent a message with a gap in it. Skipped contacts, and contacts with no WhatsApp number or email address, are set to invalid during activation. Adding the missing data later does not put them back in the queue.
Common tasks
Launch a voice campaign
Create the campaign
Creating campaigns requires a full key.
The response is 201 Created:
In this example the workspace is on Starter, so the requested maxConcurrentCalls of 5 was stored as 3, the plan’s concurrent-call limit.
Add contacts
Load the people to call. For more than a handful, use one bulk request (up to 5,000 contacts) or a CSV upload, because every route under /api/campaigns/{campaignId}/contacts is limited to 5 requests per minute. See Contacts.
Activate
The response is 200 with the campaign in data, now "status": "active". Activation checks, in this order:
- The campaign is
draftorpaused. - The agent still exists and serves the campaign’s channel.
- Voice: at least one contact is
pending. Other channels: the campaign has at least one contact. - WhatsApp with a template: the template exists, is approved, every placeholder has a value or a mapping, and your WhatsApp sender is online.
- Email: the workspace sends from its own connected mailbox, the mailbox is healthy, and subject and body are either both set or both empty.
- Voice: the number of
pendingcontacts does not exceed your plan’s contact limit. - Your plan still has a free active-campaign slot.
Activation starts real calls and messages. The request returns once the outreach is queued, which can take a few seconds for large campaigns.
Reach out on WhatsApp with per-contact values
This campaign sends an approved template whose body is Hola {{1}}, tu pedido de {{2}} está listo. Placeholder 1 is the contact’s first name, falling back to cliente. Placeholder 2 is the same for everybody.
On activation, contacts that have a valid E.164 phoneNumber but no WhatsApp number get their phone number copied into the WhatsApp field, so imported lists are reachable. The schedule is required by the schema but does not restrict WhatsApp sends.
To turn a WhatsApp campaign back into an inbound-only one, send "whatsappTemplateId": null in a PATCH. For email, send "emailSubject": null and "emailBody": null.
Turn an email campaign into outreach
PATCH accepts every create field except agentId, all optional. This request adds a subject and body to an existing email campaign, so its next activation mails every contact.
The response is 200 with the updated campaign. A PATCH with no recognized fields returns the campaign unchanged.
An email campaign needs a mailbox of your own. Connect Gmail, Outlook or Zoho Mail under Integrations, then select it as the sender under Settings → Account → Email. Connecting it is not enough. Without that, creating or activating an email campaign fails with 409 VALIDATION_FAILED; mail is never sent from a platform address instead.
Changing channel on an active campaign stops the old channel’s queued jobs but does not queue anything on the new one. Pause the campaign and activate it again so the remaining pending contacts are queued on the new channel.
Pause and resume
Pausing does not delete queued work. Each queued dial or send checks the status when its turn comes and does nothing while the campaign is paused. Calls already in progress finish normally.
A resumed voice campaign queues only the contacts still pending. People already called in the earlier run are not called again, except through the normal retry rules.
List campaigns and track progress
GET /api/campaigns uses page-number pagination: page (default 1) and limit (1 to 100, default 20), newest first.
The response has no total count: stop when a page returns fewer than limit items. GET /api/campaigns/{campaignId} returns one campaign with every field from the campaign object, in data.
Campaign reads are cached briefly: the list for up to 60 seconds and a single campaign for up to 120 seconds. Your own create, update, activate, pause and delete requests refresh the cache immediately, but changes Jelliu makes on its own (automatic completion, a system pause, contacts being worked through) can take that long to appear. Use the campaign.completed webhook rather than polling for completion.
Read campaign results
GET /api/analytics/campaigns/{campaignId} summarizes the campaign’s calls. The optional direction query parameter accepts inbound, outbound or all (default).
Results are cached for up to two minutes. For WhatsApp and email campaigns this endpoint reports zeros, because they produce conversations rather than calls; read those through Conversations. For call-by-call detail, list GET /api/calls?campaignId=....
Delete a campaign
Deletion is a soft delete: the campaign stops appearing in the API and its ID returns 404. An active campaign must be paused first.
A successful delete returns 204 No Content with an empty body.
Errors
Every error uses the standard envelope. Codes specific to campaigns:
Limits
Scopes
Adding contacts to a campaign needs only write. See Authentication.
Rate limits
- All campaign routes count against the general API limit.
- Create, update, delete, activate and pause also share the 10 per minute configuration-mutation budget with agent and webhook mutations.
- Routes under
/api/campaigns/{campaignId}/contactsare limited to 5 per minute. GET /api/analytics/campaigns/{campaignId}is limited to 30 per minute.
Plan limits
- Only
activecampaigns use a slot. Drafts, paused and completed campaigns do not. Pausing a campaign frees its slot; activating one takes it. - Creating a draft also needs a free slot. When your active campaigns already fill the plan,
POST /api/campaignsis refused with403 BILLING_ERROReven though the new campaign would start as a draft. Pause or finish a running campaign first. maxConcurrentCallsis clamped to your plan’s concurrent-call limit when you save it, so the stored value is the effective one.- A workspace with no active plan cannot create or activate campaigns.
Jelliu also creates a system campaign named Manual Conversations to hold contacts from web chat and messages started from the dashboard. It is excluded from GET /api/campaigns and never counts against your active-campaign limit.
Request bounds
Webhooks
Narrow deliveries to specific campaigns with the campaignIds filter. See Webhooks.
Related
Add, import and manage the people a campaign reaches.
The agent a campaign runs, and the channels it serves.
Call statuses, outcomes, recordings and transcripts.
WhatsApp, email and web chat threads started by campaigns.
The number voice campaigns dial from.
Every campaign endpoint, parameter and response.

