Campaigns and targets
GET /api/campaigns lists call campaigns only (type = "call"). Email campaigns are at
Email campaign endpoints.
Campaigns
GET /api/campaigns
Returns call campaigns with aggregated target counts.
[
{
"id": "uuid",
"name": "Campaign Name",
"type": "call",
"prefered_phone": "+1234567890",
"status": "running",
"max_concurrent_calls": 10,
"window_start": "09:00:00",
"window_end": "17:00:00",
"allow_weekends": false,
"max_attempts": 4,
"wait_hours_to_recall": 24,
"pending_count": 100,
"in_progress_count": 5,
"completed_count": 50,
"failed_count": 10,
"total_count": 165,
"created_at": "2026-01-01T00:00:00.000Z",
"updated_at": "2026-01-01T00:00:00.000Z"
}
]
Note prefered_phone — the misspelling matches the Prisma field.
POST /api/campaigns
- Body
-
name,max_concurrent_calls,prefered_phone,window_start,window_end,allow_weekends,max_attempts,wait_hours_to_recall - Response
-
201with a single campaign object.
GET /api/campaigns/{id}
Single campaign with target counts, including type. The dashboard at /campaigns/{id} is
voice-only, so a type of email causes a client-side redirect to /email-campaigns/{id}.
PATCH /api/campaigns/{id}
All fields optional; only supplied fields are updated. status accepts draft, running, paused,
stopped, completed.
Set max_attempts explicitly — its fallback differs between code paths. See
Call campaigns.
Targets
GET /api/campaigns/{id}/targets
Paginated targets. page (default 1), pageSize (default 50, max 200).
- Filters
-
search(phone, partial),states(repeatable),isAmazonSeller,validConversation,meetsMonthlyThreshold,stopCalling,disposition,nextAction,hubspotSyncStatus,riskLevel,maxAttempts
When the target is linked to a TargetPhoneNumbers row, registry fields are included:
target_carrier_name, target_line_type, target_risk_level, target_is_blocked,
target_phone_registry_updated_at.
{
"data": [
{
"id": "uuid",
"campaign_id": "uuid",
"phone_number": "+1234567890",
"target_carrier_name": "Example Carrier",
"target_line_type": "mobile",
"target_risk_level": "low",
"target_is_blocked": false,
"target_phone_registry_updated_at": "2026-01-01T00:00:00.000Z",
"state": "pending",
"attempts": 0,
"last_error": null,
"isAmazonSeller": null,
"meetsMonthlyThreshold": null,
"interested_in_daily_payments": null,
"stop_calling": false,
"disposition": null,
"last_call_recording_url": null,
"next_action": null,
"hubspot_contact_id": null,
"hubspot_engagement_id": null,
"hubspot_sync_status": null,
"valid_conversation": null,
"last_attempt_at": null,
"next_earliest_attempt_at": null,
"lease_expires_at": null,
"payload": {},
"tz": "America/New_York",
"created_at": "2026-01-01T00:00:00.000Z",
"updated_at": "2026-01-01T00:00:00.000Z"
}
],
"total": 100,
"page": 1,
"pageSize": 50
}
isAmazonSeller and meetsMonthlyThreshold are emitted in camelCase here, unlike the rest of the
payload.
POST /api/campaigns/{id}/targets
Single target:
{ "phone_number": "+1234567890", "state": "pending", "payload": { "first_name": "John" } }
Bulk:
{ "targets": [ { "phone_number": "+1234567890", "payload": {} } ] }
- Response
-
201with the created targets.
Behaviour:
-
Phone numbers are trimmed and validated.
-
tzis auto-detected from the area code. -
A HubSpot contact id may be supplied in
payloadunder any ofRecord ID,record_id,RecordIdorhubspot_id— the variants accommodate different HubSpot CSV exports. -
payloadis stored aspayloadJsonand is passed to Retell as dynamic variables, so its keys are addressable from the agent prompt.
Campaign calls
GET /api/campaigns/{id}/calls
Same filters as GET /api/calls minus campaignId (fixed by the URL), plus targetId. See
Call endpoints.
Coverage
Broadcast
POST /api/campaigns/{id}/broadcast
Internal only — requires Authorization: Bearer <INTERNAL_API_SECRET>. Called by pg_cron.
- Body (optional)
-
{ "campaignId": "uuid" }— falls back to the URL parameter.
The route passes a lease of 86 400 seconds, then delegates to
PhoneNumberCallingService.processBroadcast. Response is the BroadcastResult:
{
"callsInitiated": 5,
"callsSkipped": 1,
"hasAvailableTargets": true,
"message": "Campaign A - Throttled: 20/20 in-flight runs. Will retry on next broadcast tick."
}
Errors: 401 without the secret; 400 with no campaign id; 500 on an unexpected failure. A
campaign that is not running raises CallInitiationError and surfaces as 500.
See Call campaigns for the full loop.