SMS Campaigns
Create and run SMS campaigns against leads targeted by a list_id, a tag, or an explicit lead_ids array. Recipient count is resolved up front, and the request is rejected if it would resolve to zero.
Create a campaign
POST /api/v1/campaigns creates and optionally triggers a campaign. Scope: send_sms.
{
"name": "Spring Tune-Up Promo",
"message_template": "Hi {first_name}! 10% off repairs this month. Reply STOP to opt out.",
"target": {
"tag": "spring-promo-2026"
},
"schedule": "immediate",
"batch_size": 10,
"batch_delay_minutes": 5
}
nameis required.- Provide either
message_templateormessage_variations(an array of strings for A/B rotation). targetis required and takes exactly one key:tag,list_id, orlead_ids(array of lead UUIDs).schedule: "immediate"triggers the internal send worker right away. To schedule for later, pass an object instead:schedule: { "send_at": "2026-04-22T10:00:00Z", "timezone": "America/New_York" }.batch_sizeis optional (1-100, default 10);batch_delay_minutesis optional (1-60, default 5). Together they pace delivery.
{
"id": "campaign-uuid",
"name": "Spring Tune-Up Promo",
"status": "sending",
"scheduled_for": null,
"recipient_count": 473,
"selection_type": "filters",
"created_at": "2026-04-20T15:00:00Z"
}
Merge fields and the 306-character cap
Supported merge fields in message_template: {first_name}, {last_name}, {address}, {city}, {state}, {zip}, {company_name}. Unreplaced variables are stripped from the final body.
message_template and each variation are capped at 306 characters (2 SMS segments); longer templates are rejected with 400.
Daily send cap
Outbound campaign messages are capped at 400 per tenant per day by default. If you hit the cap, contact support to raise it. Opt-out records are enforced per recipient before each send, so no additional client-side check is needed.
List campaigns
GET /api/v1/campaigns lists SMS campaigns with pagination. Scope: read.
| Parameter | Description |
|---|---|
limit | Results per page (1-100, default 50) |
cursor | Pagination cursor from the previous response |
status | Filter: draft, scheduled, sending, completed, paused, failed |
{
"data": [
{
"id": "uuid",
"name": "Spring Tune-Up Promo",
"status": "completed",
"total_recipients": 473,
"messages_sent": 473,
"messages_delivered": 461,
"messages_failed": 2,
"replies_received": 38,
"opt_outs": 4,
"selection_type": "filters",
"created_at": "2026-04-20T15:00:00Z"
}
],
"has_more": false
}
Get a campaign (live stats)
GET /api/v1/campaigns/:id returns campaign detail with live delivery stats recomputed from the underlying messages. Scope: read.
{
"id": "uuid",
"name": "Spring Tune-Up Promo",
"status": "completed",
"message_template": "Hi {first_name}! 10% off...",
"message_variations": ["Hi {first_name}! 10% off..."],
"target": {
"selection_type": "filters",
"selection_criteria": { "tag": "spring-promo-2026" },
"list_id": null
},
"scheduled_for": null,
"started_at": "2026-04-20T15:00:30Z",
"completed_at": "2026-04-20T16:12:07Z",
"recipient_count": 473,
"stats": {
"sent": 473,
"delivered": 461,
"failed": 2,
"pending": 10,
"replies": 38,
"opt_outs": 4
},
"batch_size": 10,
"batch_delay_minutes": 5,
"created_at": "2026-04-20T15:00:00Z",
"updated_at": "2026-04-20T16:12:07Z"
}
Update a campaign (PATCH contract)
PATCH /api/v1/campaigns/:id updates a campaign's mutable fields and/or transitions its status. Scope: write_crm.
The body takes at least one of two top-level keys: field_updates (an object of field edits) and status_action ("pause" | "resume" | "cancel").
field_updatesis only accepted while the campaign isdraftorscheduled:name,message_template(max 306 chars),message_variations,schedule("immediate"or an object withsend_atandtimezone),batch_size(1-100),batch_delay_minutes(1-60). Otherwise returns409 not_editable_in_current_status.status_action: "pause"halts asendingcampaign (resumable)."resume"restarts apausedone."cancel"is terminal. Invalid transitions return409 invalid_status_transition.completed,cancelled, andfailedcampaigns are immutable.
{
"field_updates": {
"name": "Spring Promo (updated)",
"message_template": "New copy...",
"message_variations": ["A", "B"],
"schedule": { "send_at": "2026-04-25T10:00:00Z", "timezone": "America/New_York" },
"batch_size": 20,
"batch_delay_minutes": 3
},
"status_action": "pause"
}
{
"campaign": {
"id": "uuid",
"name": "Spring Promo (updated)",
"status": "paused",
"message_template": "New copy...",
"message_variations": null,
"scheduled_for": "2026-04-25T10:00:00Z",
"started_at": null,
"completed_at": null,
"batch_size": 20,
"batch_delay_minutes": 3,
"updated_at": "2026-04-20T16:30:00Z"
},
"changed_fields": ["name", "message_template", "status"]
}
curl -X PATCH https://app.autorev.ai/api/v1/campaigns/<id> \
-H "Authorization: Bearer ar_live_..." \
-H "Content-Type: application/json" \
-d '{ "status_action": "pause" }'
Resuming a paused campaign still respects the tenant's daily outbound cap (default 400/day). Messages already in flight when you pause are not recalled.