Skip to main content

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.

Request body
{
"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
}
  • name is required.
  • Provide either message_template or message_variations (an array of strings for A/B rotation).
  • target is required and takes exactly one key: tag, list_id, or lead_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_size is optional (1-100, default 10); batch_delay_minutes is optional (1-60, default 5). Together they pace delivery.
Response (202 Accepted)
{
"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

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.

ParameterDescription
limitResults per page (1-100, default 50)
cursorPagination cursor from the previous response
statusFilter: draft, scheduled, sending, completed, paused, failed
Response
{
"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.

Response
{
"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_updates is only accepted while the campaign is draft or scheduled: name, message_template (max 306 chars), message_variations, schedule ("immediate" or an object with send_at and timezone), batch_size (1-100), batch_delay_minutes (1-60). Otherwise returns 409 not_editable_in_current_status.
  • status_action: "pause" halts a sending campaign (resumable). "resume" restarts a paused one. "cancel" is terminal. Invalid transitions return 409 invalid_status_transition.
  • completed, cancelled, and failed campaigns are immutable.
Request body
{
"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"
}
Response
{
"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"]
}
Example: pause an in-flight campaign
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" }'
Pause and resume behavior

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.