Campaign API guide
Add customers, restart campaign workflows, and set enrollment status using an API key.
Use campaign actions to connect your own trigger logic to careCycle. Customers can be identified by their careCycle customer ID or US phone number. Every action accepts one customer or a list of up to 1,000 input entries.
Authentication
Create a key in Developer Settings
with the campaign_enrollment.manage capability. Include it in x-api-key.
The same capability authorizes result polling. Add campaigns.view if your
integration needs to discover campaign IDs through the campaigns API.
All campaign and customer references are scoped to the key's organization. Read-only playground keys cannot perform these actions.
Endpoints
| Method | Path | Action |
|---|---|---|
| POST | /v2/campaigns/{campaignId}/enrollment | Add customers |
| POST | /v2/campaigns/{campaignId}/enrollment/restart | Restart existing enrollments |
| POST | /v2/campaigns/{campaignId}/enrollment/status | Set terminal enrollment status |
| GET | /v2/campaigns/{campaignId}/enrollment/requests/{jobId} | Read progress and results |
Identify customers
Each reference must contain exactly one field:
{ "customerId": "223e4567-e89b-42d3-a456-426614174000" }{ "phoneNumber": "+12025550123" }US numbers such as 2025550123, 12025550123, and (202) 555-0123 are
normalized to +12025550123. International numbers outside the +1 numbering
format are not supported by this lookup.
Customers must already exist. These endpoints do not create customers or restore
deleted records. A missing, deleted, or other-organization customer produces
customer_not_found. Malformed customer entries fail individually with
invalid_customer_reference and field-level validationErrors. Valid entries
continue processing. For example, two malformed entries in a 1,000-entry list
produce two item failures while the other 998 are processed normally.
The request still returns 202 with a job ID, even if all customer entries are
invalid. Poll for results and correlate failures using each item's zero-based
index. Request-level errors, such as an invalid campaign ID, providing both
customer and customers, an empty or oversized list, or an unsupported terminal
status, reject the whole request with 422.
Provide either customer or customers, never both. A list may mix IDs and
phone numbers. If multiple references resolve to the same customer, its action
runs once. Results retain every input position and identify duplicates with
duplicateOf, the zero-based index of the first reference.
Add one customer
curl --request POST \
"https://api.carecycle.ai/v2/campaigns/$CAMPAIGN_ID/enrollment" \
--header "x-api-key: $CARECYCLE_API_KEY" \
--header "Content-Type: application/json" \
--data '{
"customer": { "phoneNumber": "+12025550123" }
}'Both individual and list requests return HTTP 202 Accepted with a job ID:
{
"jobId": "123e4567-e89b-42d3-a456-426614174000",
"statusUrl": "/v2/campaigns/423e4567-e89b-42d3-a456-426614174000/enrollment/requests/123e4567-e89b-42d3-a456-426614174000",
"total": 1,
"resolvedCount": 1
}total counts input entries. resolvedCount counts unique existing customers
that matched. Acceptance does not mean the workflow has executed or a call has
been placed. Even requests where no customers match return a job ID with final
per-item failures available through polling.
Change status for a list
POST to /v2/campaigns/{campaignId}/enrollment/status:
{
"customers": [
{ "customerId": "223e4567-e89b-42d3-a456-426614174000" },
{ "phoneNumber": "+12025550124" }
],
"status": "cancelled",
"reason": "Outreach completed in our CRM"
}Allowed statuses are completed, failed, and cancelled. reason is optional
and limited to 255 characters. This changes the customer's enrollment, not the
campaign itself or the customer's overall CRM status. Customers not enrolled in
this campaign are skipped.
Setting a terminal status stops future campaign work and removes queued campaign events. It does not hang up a call already in progress or cancel separately scheduled appointments or inquiry callbacks.
Restart
POST to /v2/campaigns/{campaignId}/enrollment/restart:
{
"customer": { "phoneNumber": "+12025550123" }
}Restart requires an existing enrollment and a Pulse workflow. It reuses the enrollment record, clears workflow execution and failure state, and starts from the currently published workflow's entry node when the campaign is active. Customers not enrolled are skipped. Historical calls and enrollment history are preserved. Restart is not a full reset of customer data, call attempts, or appointment-booked information.
Adding a customer who is already active returns already_active. Adding someone
whose enrollment has finished returns terminal_exists. Neither operation
implicitly restarts them; use the restart endpoint explicitly.
Campaign restrictions
- Active campaigns can execute newly enrolled or restarted customers.
- Draft and paused campaigns accept enrollment, but workflow execution waits for the campaign to become active.
- Completed, cancelled, archived, and deleted campaigns cannot receive new enrollments or restarts.
- Receptionist campaign enrollments are managed automatically and cannot be changed through these endpoints.
- A customer can have only one non-terminal retention journey at a time. A non-terminal enrollment in a draft or paused retention journey also occupies that slot. Retention notification campaigns are non-exclusive overlays.
- There is one enrollment record per customer/campaign pair. Restart does not create a second simultaneous journey in the same campaign.
- Required customer fields, workflow rules, calling hours, and call/SMS eligibility still apply. Enrollment does not bypass them.
Poll results
GET the returned statusUrl with the same API-key header. Poll every 2-5 seconds
until status is completed, partial, or failed. Polls count toward the key's
request limit.
{
"jobId": "123e4567-e89b-42d3-a456-426614174000",
"campaignId": "423e4567-e89b-42d3-a456-426614174000",
"action": "enroll",
"status": "completed",
"total": 1,
"processed": 1,
"succeeded": 1,
"skipped": 0,
"failed": 0,
"completedAt": "2026-10-01T17:00:00.000Z",
"items": [
{
"index": 0,
"customerId": "223e4567-e89b-42d3-a456-426614174000",
"duplicateOf": null,
"outcome": "succeeded",
"code": "enrolled",
"customerCampaignId": "523e4567-e89b-42d3-a456-426614174000"
}
]
}Results and counts refer to input positions, including duplicates. Items remain
in input order. pending means an outcome is not available yet. A completed job
can contain skips; always inspect item outcomes. Processing is per customer and
is not an all-or-nothing transaction.
| Code | Meaning |
|---|---|
enrolled | Created an enrollment |
restarted | Restarted an existing enrollment |
status_changed | Applied the requested terminal status |
already_active | Enrollment already active; no new enrollment |
terminal_exists | Enrollment already finished; explicit restart required |
not_enrolled | Restart/status skipped because no enrollment exists |
customer_not_found | No available customer in this organization |
invalid_customer_reference | Malformed customer entry; inspect validationErrors for the field and explanation |
blocked_active_retention | Another non-terminal retention journey blocks this action |
missing_required_fields | Enrollment cannot proceed until required customer fields are populated |
campaign_unavailable | Campaign became unavailable or ineligible before processing |
no_pulse_workflow | No workflow is available to restart |
not_processed | The job failed before this item was attempted |
execution_uncertain | An attempt was interrupted; review the enrollment before requesting another action |
Results are persisted independently of the queue's 24-hour completed-job
retention. Requests are scoped to both organization and campaign; inaccessible
job IDs return 404.
Requests and optional retry protection
You do not need to generate an idempotency key to use these endpoints.
careCycle creates the request identity and returns a job ID for polling. Without
an idempotencyKey, each POST is a new request. A repeated add does not create
another enrollment for the same customer/campaign pair; a repeated restart is a
new restart action.
For integrations that automatically retry requests after timeouts, optionally
include an idempotencyKey in the JSON body. An existing upstream event ID
can serve as the key. The key is scoped to your organization across all campaign
action endpoints. Reuse it for retries of one logical request, and use a new key
for a new action.
{
"customer": { "phoneNumber": "+12025550123" },
"idempotencyKey": "upstream-event-123"
}- With a key, retry the same payload and key after a timeout. The original job ID and original resolved customer selection are reused.
- Reusing the key with a different campaign, action, customer selection, status,
or reason returns
409 IDEMPOTENCY_CONFLICT. - Completed items are not executed again when a job is replayed.
- If a process fails after claiming an item but before recording its outcome,
careCycle reports
execution_uncertaininstead of automatically restarting the customer again. Check the enrollment before issuing a new key. - To retry known failed items after correcting their data, send only those customers in a new request. If using retry keys, supply a new key for the corrected payload. Do not include already-successful customers in a new restart.
- If a job remains queued because publication failed or its queue entry was lost, a keyed request can be republished by retrying its original POST and key.
- Without a retry key, do not blindly retry a timed-out restart: it may have already been accepted. If you received a job ID, poll it; otherwise check the enrollment before initiating another restart.
Lists above 1,000 entries are rejected rather than truncated. Split larger sets
into smaller batches and track each job separately. If using retry keys, give
each batch its own key. Respect 429 responses and their retryAfter value.