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.