Campaigns

Run one agent against a list of contacts, on one channel, inside a schedule you control.
View as Markdown

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

  1. Create the campaign. It is stored in draft and nothing is sent.
  2. Add contacts to it, one at a time, in bulk or from a CSV file. See Contacts.
  3. Activate it. Jelliu runs the channel’s preflight checks, flips the status to active and queues the outreach.
  4. 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.
  5. Completion is automatic. A campaign moves to completed when it has no pending contacts 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.

Current statusRequestResult
draftPATCH /api/campaigns/{campaignId}/activateactive, and outreach is queued.
pausedPATCH /api/campaigns/{campaignId}/activateactive. Only contacts still pending are queued again, so the run resumes where it stopped.
activePATCH /api/campaigns/{campaignId}/activate200 with the campaign unchanged. Nothing is queued a second time.
completed, archivedPATCH /api/campaigns/{campaignId}/activate400 CAMPAIGN_NOT_ACTIVE: Campaign can only be activated from DRAFT or PAUSED status
activePATCH /api/campaigns/{campaignId}/pausepaused.
any otherPATCH /api/campaigns/{campaignId}/pause400 CAMPAIGN_NOT_ACTIVE: Campaign can only be paused when ACTIVE
activenone (automatic)completed once no contact is pending and no call is in flight.
activenone (automatic)paused with blocked_reason set, when the problem belongs to the workspace rather than to a contact. See Paused by Jelliu.

Two other rules depend on the status:

  • PATCH /api/campaigns/{campaignId} is refused for completed and archived campaigns.
  • DELETE /api/campaigns/{campaignId} is refused for active campaigns. 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

ChannelWhat activation does
voiceQueues one dial per pending contact. Calls are placed within the schedule, paced by the concurrency limits, and retried when nobody answers.
whatsappWith whatsappTemplateId set: sends that approved template to every contact. Without it: the campaign is inbound-only and the agent answers people who write to your WhatsApp number.
emailWith both emailSubject and emailBody set: sends the email to every contact. With neither: inbound-only. Setting just one of them is refused at activation.
webchatInbound-only. Activation arms the agent; nothing is sent.

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:

  1. The campaign is still active and still a voice campaign. Otherwise the job ends and the contact stays pending.
  2. 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.
  3. The contact is still pending, and its current phone number is used, not the one it had when the campaign was activated.
  4. The number is dialable. A value that is not E.164, or that the carrier lookup reports as unroutable, marks the contact invalid without spending an attempt.
  5. Consent. If the workspace requires consent before calling, a contact without voice-call consent is marked invalid.
  6. Claim. The contact is atomically moved to called, so two workers can never dial the same person.
  7. 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.

FieldTypeNullableDescription
idstring (uuid)NoUnique identifier.
tenant_idstring (uuid)NoThe workspace that owns the campaign.
agent_idstring (uuid)NoThe agent that runs the campaign. Cannot be changed after creation.
namestringNo1 to 200 characters. Reaches the agent as campaign_name.
product_contextstringNo10 to 5000 characters. Reaches the agent as product_context.
target_audiencestringNo1 to 1000 characters. Reaches the agent as target_audience.
statusstringNodraft, active, paused, completed or archived.
blocked_reasonstringYesWhy Jelliu paused the campaign, in Spanish, naming what to fix. null when a person paused it or it was never paused by the system.
channelstringNovoice, whatsapp, email or webchat.
categorystringNosales, support, scheduling, surveys, collections, retention, notifications, interview, language_assessment or general. Decides which call outcomes count as a success.
scheduleobjectNo{ timezone, days }. See Schedule.
max_concurrent_callsintegerNoLive calls this campaign may hold at once. Stored already clamped to your plan’s concurrent-call limit.
max_retry_attemptsintegerNoTotal dials per contact in a run, including the first. 0 disables retries.
retry_interval_minutesintegerNoMinutes between a missed call and the next dial.
whatsapp_template_idstring (uuid)YesWhatsApp only. The approved template sent on activation. null means inbound-only.
whatsapp_template_variablesarray or objectYesWhatsApp only. Campaign-wide values for the template placeholders.
whatsapp_template_variable_mapobjectYesWhatsApp only. Placeholders filled per contact. See Template variables.
email_subjectstringYesEmail only. Subject line, may contain tokens.
email_bodystringYesEmail only. Message body, may contain tokens.
retry_policyobjectYesLegacy column. Not settable through the API; ignore it.
voicemail_actionstringYesLegacy column. Not settable through the API; ignore it.
voicemail_messagestringYesLegacy column. Not settable through the API; ignore it.
created_atstring (date-time)NoWhen the campaign was created.
updated_atstring (date-time)NoLast change to the row.
deleted_atstring (date-time)YesAlways null in responses: deleted campaigns are not returned.

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.

FieldTypeNullableDescription
agent_namestringYesName of the campaign’s agent.
total_contactsintegerNoContacts in the campaign.
pending_contactsintegerNoContacts with status pending.
called_contactsintegerNoContacts that have been dialed at least once.
converted_contactsintegerNoContacts with status converted.
conversion_ratenumberNoconverted_contacts divided by called_contacts, as a percentage with one decimal. 0 when nobody has been called.

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

FieldTypeRules
timezonestringA valid IANA time zone, for example America/Bogota.
daysarrayAt least one item, and at least one item with enabled: true.
days[].daystringmonday through sunday.
days[].startHourinteger0 to 23. The first hour calls may start, in timezone.
days[].endHourinteger1 to 24. Calls start only before this hour.
days[].enabledbooleanDays with false are skipped. On enabled days startHour must be lower than endHour.

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}}.

TokenResolves to
contact.nameThe contact’s name.
contact.first_nameThe first word of the contact’s name.
contact.emailThe contact’s email.
contact.phoneThe contact’s WhatsApp number, or phone number, when it is a real E.164 number.
campaign.nameThe campaign’s name.
contact.metadata.KEYA custom field of the contact, where KEY is 1 to 64 letters, digits, spaces, _, . or -. CSV imports store column headers here.

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

1

Create the campaign

Creating campaigns requires a full key.

curl -sS -X POST "https://api.jelliu.co/api/campaigns" \
-H "Authorization: Bearer $JELLIU_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"agentId": "7c1d9f30-5e2a-4a7e-9c8f-91a0b1c2d3e4",
"name": "Renovaciones septiembre",
"productContext": "Llamadas a clientes cuyo plan de internet hogar vence este mes para ofrecer la renovación con 20% de descuento.",
"targetAudience": "Clientes residenciales con contrato por vencer",
"channel": "voice",
"category": "retention",
"maxConcurrentCalls": 5,
"maxRetryAttempts": 3,
"retryIntervalMinutes": 120,
"schedule": {
"timezone": "America/Bogota",
"days": [
{ "day": "monday", "startHour": 9, "endHour": 18, "enabled": true },
{ "day": "tuesday", "startHour": 9, "endHour": 18, "enabled": true },
{ "day": "wednesday", "startHour": 9, "endHour": 18, "enabled": true },
{ "day": "thursday", "startHour": 9, "endHour": 18, "enabled": true },
{ "day": "friday", "startHour": 9, "endHour": 17, "enabled": true },
{ "day": "saturday", "startHour": 9, "endHour": 13, "enabled": false }
]
}
}'

The response is 201 Created:

{
"data": {
"id": "0f3c8b52-6d1a-4e2f-9b7c-4a5e6d7f8a90",
"tenant_id": "3e9a1c47-2b8d-4f60-a5c1-7d2e9b4f8a13",
"agent_id": "7c1d9f30-5e2a-4a7e-9c8f-91a0b1c2d3e4",
"blocked_reason": null,
"name": "Renovaciones septiembre",
"product_context": "Llamadas a clientes cuyo plan de internet hogar vence este mes para ofrecer la renovación con 20% de descuento.",
"target_audience": "Clientes residenciales con contrato por vencer",
"status": "draft",
"schedule": {
"timezone": "America/Bogota",
"days": [
{ "day": "monday", "startHour": 9, "endHour": 18, "enabled": true },
{ "day": "tuesday", "startHour": 9, "endHour": 18, "enabled": true },
{ "day": "wednesday", "startHour": 9, "endHour": 18, "enabled": true },
{ "day": "thursday", "startHour": 9, "endHour": 18, "enabled": true },
{ "day": "friday", "startHour": 9, "endHour": 17, "enabled": true },
{ "day": "saturday", "startHour": 9, "endHour": 13, "enabled": false }
]
},
"max_concurrent_calls": 3,
"max_retry_attempts": 3,
"retry_interval_minutes": 120,
"channel": "voice",
"category": "retention",
"retry_policy": {},
"voicemail_action": "retry",
"voicemail_message": null,
"whatsapp_template_id": null,
"whatsapp_template_variables": null,
"whatsapp_template_variable_map": null,
"email_subject": null,
"email_body": null,
"created_at": "2026-09-15T14:02:11.482Z",
"updated_at": "2026-09-15T14:02:11.482Z",
"deleted_at": null
}
}

In this example the workspace is on Starter, so the requested maxConcurrentCalls of 5 was stored as 3, the plan’s concurrent-call limit.

2

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.

curl -sS -X POST "https://api.jelliu.co/api/campaigns/0f3c8b52-6d1a-4e2f-9b7c-4a5e6d7f8a90/contacts" \
-H "Authorization: Bearer $JELLIU_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "phoneNumber": "+573001234567", "name": "Ana Gómez" }'
3

Activate

curl -sS -X PATCH "https://api.jelliu.co/api/campaigns/0f3c8b52-6d1a-4e2f-9b7c-4a5e6d7f8a90/activate" \
-H "Authorization: Bearer $JELLIU_API_KEY"

The response is 200 with the campaign in data, now "status": "active". Activation checks, in this order:

  1. The campaign is draft or paused.
  2. The agent still exists and serves the campaign’s channel.
  3. Voice: at least one contact is pending. Other channels: the campaign has at least one contact.
  4. WhatsApp with a template: the template exists, is approved, every placeholder has a value or a mapping, and your WhatsApp sender is online.
  5. Email: the workspace sends from its own connected mailbox, the mailbox is healthy, and subject and body are either both set or both empty.
  6. Voice: the number of pending contacts does not exceed your plan’s contact limit.
  7. 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.

curl -sS -X POST "https://api.jelliu.co/api/campaigns" \
-H "Authorization: Bearer $JELLIU_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"agentId": "7c1d9f30-5e2a-4a7e-9c8f-91a0b1c2d3e4",
"name": "Pedidos listos para recoger",
"productContext": "Avisar a clientes que su pedido está listo y resolver dudas sobre horarios de recogida.",
"targetAudience": "Clientes con pedidos listos en tienda",
"channel": "whatsapp",
"category": "notifications",
"whatsappTemplateId": "9b2e4d61-8f3a-4c7b-b1e5-2a6d9c0f4e87",
"whatsappTemplateVariables": { "2": "Tienda Chapinero" },
"whatsappTemplateVariableMap": {
"1": { "token": "contact.first_name", "fallback": "cliente" }
},
"schedule": {
"timezone": "America/Bogota",
"days": [{ "day": "monday", "startHour": 8, "endHour": 20, "enabled": true }]
}
}'

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.

curl -sS -X PATCH "https://api.jelliu.co/api/campaigns/0f3c8b52-6d1a-4e2f-9b7c-4a5e6d7f8a90" \
-H "Authorization: Bearer $JELLIU_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"emailSubject": "{{contact.first_name}}, tu renovación está lista",
"emailBody": "Hola {{contact.first_name}}, responde a este correo y te ayudamos a renovar tu plan hoy mismo."
}'

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.

# Pause
curl -sS -X PATCH "https://api.jelliu.co/api/campaigns/0f3c8b52-6d1a-4e2f-9b7c-4a5e6d7f8a90/pause" \
-H "Authorization: Bearer $JELLIU_API_KEY"
# Resume: activate again
curl -sS -X PATCH "https://api.jelliu.co/api/campaigns/0f3c8b52-6d1a-4e2f-9b7c-4a5e6d7f8a90/activate" \
-H "Authorization: Bearer $JELLIU_API_KEY"

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.

curl -sS "https://api.jelliu.co/api/campaigns?page=1&limit=20" \
-H "Authorization: Bearer $JELLIU_API_KEY"
{
"data": [
{
"id": "0f3c8b52-6d1a-4e2f-9b7c-4a5e6d7f8a90",
"tenant_id": "3e9a1c47-2b8d-4f60-a5c1-7d2e9b4f8a13",
"agent_id": "7c1d9f30-5e2a-4a7e-9c8f-91a0b1c2d3e4",
"name": "Renovaciones septiembre",
"status": "active",
"category": "retention",
"channel": "voice",
"max_concurrent_calls": 3,
"max_retry_attempts": 3,
"schedule": {
"timezone": "America/Bogota",
"days": [{ "day": "monday", "startHour": 9, "endHour": 18, "enabled": true }]
},
"created_at": "2026-09-15T14:02:11.482Z",
"updated_at": "2026-09-15T14:05:40.019Z",
"deleted_at": null,
"blocked_reason": null,
"agent_name": "Laura - Renovaciones",
"total_contacts": 250,
"pending_contacts": 164,
"called_contacts": 86,
"converted_contacts": 12,
"conversion_rate": 14
}
],
"meta": { "page": 1, "limit": 20 }
}

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).

curl -sS "https://api.jelliu.co/api/analytics/campaigns/0f3c8b52-6d1a-4e2f-9b7c-4a5e6d7f8a90" \
-H "Authorization: Bearer $JELLIU_API_KEY"
{
"data": {
"campaignId": "0f3c8b52-6d1a-4e2f-9b7c-4a5e6d7f8a90",
"channel": "voice",
"category": "retention",
"totalInteractions": 131,
"completedInteractions": 86,
"successes": 12,
"successRate": 13.95,
"successRateLabel": "retentionRate",
"averageDurationSeconds": 143,
"avgSentiment": 0.21,
"outcomeBreakdown": {
"customer_retained": 11,
"customer_reactivated": 1,
"callback_scheduled": 9,
"rejected": 31,
"no_answer": 34
},
"excludedMetrics": []
}
}
FieldDescription
totalInteractionsCalls in the campaign, any status.
completedInteractionsCalls with status completed.
successesCalls whose outcome counts as a success for the campaign’s category. For retention, that is customer_retained and customer_reactivated; for sales, sale_closed.
successRatesuccesses divided by completedInteractions, as a percentage from 0 to 100 with two decimals.
successRateLabelWhat the rate means for the category, for example conversionRate, resolutionRate, bookingRate or retentionRate.
averageDurationSecondsAverage length of completed calls. null for non-voice campaigns.
avgSentimentAverage sentiment from -1 to 1 over calls that were scored. null when none were, never 0 for “no data”.
outcomeBreakdownCount of calls per outcome. See Calls for the outcome values.
excludedMetricsMetrics that do not apply to this channel or category.
_degradedPresent only when part of the computation failed; lists which parts. Treat the related fields as incomplete.

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.

curl -sS -X DELETE "https://api.jelliu.co/api/campaigns/0f3c8b52-6d1a-4e2f-9b7c-4a5e6d7f8a90" \
-H "Authorization: Bearer $JELLIU_API_KEY"

A successful delete returns 204 No Content with an empty body.

Errors

Every error uses the standard envelope. Codes specific to campaigns:

CodeStatusWhen
VALIDATION_FAILED400The body failed the schema (Invalid campaign input on create, Invalid update input on update, with field errors in details), or the campaign ID is not a UUID (Invalid campaign ID).
VALIDATION_FAILED400The agent does not serve the campaign’s channel.
VALIDATION_FAILED400An unknown token in emailSubject, emailBody or whatsappTemplateVariableMap, or a mapped placeholder the template does not have.
VALIDATION_FAILED400Activation: Campaign has no pending contacts to call (voice), or the campaign has no contacts (other channels).
VALIDATION_FAILED400Activation: the WhatsApp template is missing, not approved, has uncovered placeholders, or the WhatsApp sender is not online.
VALIDATION_FAILED400Activation: only one of emailSubject and emailBody is set, or the sending mailbox reports a problem.
VALIDATION_FAILED400Activation: the voice campaign has more pending contacts than your plan allows.
VALIDATION_FAILED400Cannot update a completed or archived campaign, or Cannot delete an active campaign — pause it first.
VALIDATION_FAILED409An email campaign was created or activated without a selected mailbox of your own, or the mailbox could not be checked.
CAMPAIGN_NOT_ACTIVE400Activating a campaign that is not draft or paused, or pausing one that is not active.
CAMPAIGN_NOT_ACTIVE409Campaign status changed concurrently: another request changed the status first. Read the campaign again.
BILLING_ERROR403Creating or activating would exceed the plan’s active-campaign limit, or there is no active plan. metadata carries limit, current and tier.
FORBIDDEN403A read or write key called a mutating campaign route. These require full.
CAMPAIGN_NOT_FOUND404The campaign does not exist, was deleted, or belongs to another workspace.
AGENT_NOT_FOUND404The agentId on create, or the campaign’s agent on activation or channel change, does not exist in your workspace.
RATE_LIMIT_EXCEEDED429More than 10 campaign mutations in a minute. See Limits.

Limits

Scopes

OperationMinimum scope
GET /api/campaigns, GET /api/campaigns/{campaignId}, GET /api/analytics/campaigns/{campaignId}read
POST /api/campaigns, PATCH /api/campaigns/{campaignId}, DELETE /api/campaigns/{campaignId}full
PATCH /api/campaigns/{campaignId}/activate, PATCH /api/campaigns/{campaignId}/pausefull

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}/contacts are limited to 5 per minute.
  • GET /api/analytics/campaigns/{campaignId} is limited to 30 per minute.

Plan limits

LimitStarterGrowthBusinessEnterprise
Active campaigns13UnlimitedUnlimited
Concurrent calls (workspace)31025No plan limit
Pending contacts per voice activation5002,00020,000999,999
  • Only active campaigns 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/campaigns is refused with 403 BILLING_ERROR even though the new campaign would start as a draft. Pause or finish a running campaign first.
  • maxConcurrentCalls is 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

FieldBoundsDefault
name1 to 200 charactersRequired
productContext10 to 5000 charactersRequired
targetAudience1 to 1000 charactersRequired
maxConcurrentCalls1 to 500, then clamped to the plan10
maxRetryAttempts0 to 103
retryIntervalMinutes5 to 144060
channelvoice, whatsapp, webchat, emailvoice
categorySee the objectsales
emailSubject1 to 300 charactersNone
emailBody1 to 20000 charactersNone

Webhooks

EventSentWhen
campaign.completedYesThe campaign moved to completed. data carries campaignId and campaignName.
call.completed, call.failedYesA campaign call ended. data.campaignId identifies the campaign.
campaign.started, campaign.pausedNot yetAccepted in subscriptions but not delivered today. Read status from the API instead.

Narrow deliveries to specific campaigns with the campaignIds filter. See Webhooks.