Campaigns & Recipients
Ragtime models one-to-one outreach as campaigns and recipients:
- A campaign is a scheduled outreach with its own target channel, schedule (start/end), budget, and lifecycle (
draft → upcoming → active → completed). Campaigns are created and configured in the dashboard. - A recipient is a personalized chat link (
/i/<token>) inside a campaign — one per customer — carrying its own per-person instructions and interaction limits. Recipients are created and managed through the v1 API (or the dashboard).
Each recipient inherits its campaign’s channel as the rendering surface, so it takes on that channel’s styling, languages, voice, and access rules.
Typical use case: a one-to-one outreach where you create one link per contact (keyed by email), optionally pre-loading each link with personalized context (“greet them by name, reference their recent order”) and capping how often they can interact.
How it works
Section titled “How it works”- Create a campaign in the dashboard (choose its channel, schedule, and budget). It starts as a draft.
- While the campaign is a draft or upcoming, create recipients in it via the API — you get back a unique
link(containing an unguessable token). - Launch the campaign in the dashboard. Its links go live while it is active.
- Share each link with your customer (e.g. by email).
- When they open it during the active window, the assistant applies the recipient’s stored instructions and enforces its limits.
- You can list, update, disable, or delete recipients while the campaign is editable.
Scopes
Section titled “Scopes”| Scope | Allows |
|---|---|
recipients:read |
List and fetch recipients and their state. |
recipients:write |
Create, update, and delete recipients. |
See API Keys to create a key with these scopes.
List campaigns
Section titled “List campaigns”Recipients are scoped to a campaign, so you first need a campaign id. Campaigns are created in the dashboard, but you can list them (with their current state and id) via the API:
curl https://your-ragtime-instance.com/api/v1/projects/{projectId}/campaigns \ -H "Authorization: Bearer rt_live_YOUR_KEY_HERE"{ "projectId": "...", "campaigns": [ { "id": "c1a2b3...", "name": "Spring 2026 outreach", "state": "active", "budgetMode": "metered", "deviceId": "d4e5f6...", "startDate": "2026-07-01T00:00:00Z", "endDate": "2026-07-31T00:00:00Z", "recipientCount": 42, "timing": { "state": "active", "now": "2026-07-02T12:00:00Z", "startsInSeconds": null, "runningForSeconds": 129600, "endedAgoSeconds": null } } ]}Use the id of a campaign whose state is draft or upcoming as the campaignId when creating recipients. Creating a campaign, launching it, setting its budget, and halting it are dashboard actions and are not exposed in the v1 API.
Dates and timezones
Section titled “Dates and timezones”startDate and endDate are absolute ISO 8601 instants (returned in UTC, e.g. 2026-07-01T00:00:00Z). They represent a single point in time, so the campaign transitions between states at the same instant for everyone, regardless of where the caller or their customers are located. Format them into any local timezone you like on your side.
Campaign timing
Section titled “Campaign timing”Every campaign includes a timing object so you don’t have to do date math to answer “how long until it starts / how long has it been running / how long ago did it end.” All durations are whole seconds, and only the field relevant to the current state is populated (the rest are null):
| Field | Populated when | Meaning |
|---|---|---|
state |
always | The campaign’s lifecycle state. |
now |
always | Server time (UTC) the values were computed at. |
startsInSeconds |
upcoming |
Seconds until the campaign starts. |
runningForSeconds |
active |
Seconds the campaign has been running. |
endedAgoSeconds |
completed |
Seconds since the campaign ended (or was halted). |
Because the values are language-agnostic seconds, you can render them however suits your integration (e.g. "Launching in 4d 3h", "Ended 31m ago").
Create recipients
Section titled “Create recipients”Send a POST with the target campaignId and an items array of 1 to 200 recipients. Use a one-item array to create a single recipient. The rendering channel is inherited from the campaign.
curl -X POST https://your-ragtime-instance.com/api/v1/projects/{projectId}/recipients \ -H "Authorization: Bearer rt_live_YOUR_KEY_HERE" \ -H "Content-Type: application/json" \ -d '{ "campaignId": "c1a2b3...", "items": [ { "externalId": "jane@northwind.example", "label": "Jane Doe", "metadata": { "company": "Northwind", "plan": "Pro" }, "maxTotalInteractions": 5, "maxInteractionsPerWindow": 1, "windowSeconds": 86400, "injectionMode": "append", "injectionInstructions": "The customer is {label} from {meta.company}. Greet her by name and reference her recent onboarding." } ] }'The response always has the same shape, whether you send one item or many. Each entry in results carries the shareable link:
// 201 Created{ "created": 1, "failed": 0, "total": 1, "results": [ { "status": "created", "id": "a1b2c3d4-...", "campaignId": "c1a2b3...", "externalId": "jane@northwind.example", "label": "Jane Doe", "metadata": { "company": "Northwind", "plan": "Pro" }, "deviceId": "d4e5f6...", "isActive": true, "link": "https://your-ragtime-instance.com/i/8f3aK2...token...", "injectionMode": "append", "injectionInstructions": "The customer is Jane Doe from Northwind...", "maxTotalInteractions": 5, "maxInteractionsPerWindow": 1, "windowSeconds": 86400, "interactionCount": 0, "lastInteractionAt": null, "disabledMessage": null, "cooldownMessage": null, "distillationStatus": "none", "distillationLensId": null, "distillationError": null, "createdAt": "2026-06-29T10:00:00Z" } ]}The request returns 201 when at least one recipient was created, and 422 when every item failed. Items that fail are reported in results with "status": "duplicate" (an externalId already in the campaign) or "status": "error" with an error message, without failing the rest of the batch.
The campaign referenced by campaignId must be editable (draft or upcoming). Creating recipients in an active or completed campaign returns 409.
Request body
Section titled “Request body”| Field | Type | Required | Description |
|---|---|---|---|
campaignId |
string | Yes | The campaign the recipients belong to. Must be draft or upcoming. |
items |
array | Yes | 1 to 200 recipients, each with the fields below. Unknown fields are rejected. |
Each item accepts:
| Field | Type | Required | Description |
|---|---|---|---|
externalId |
string | No | Your own identifier (e.g. email). Unique per campaign; enables idempotent batches. Max 191 chars. |
label |
string | No | Display name for the recipient. Max 191 chars. |
metadata |
object | No | Flat map of string values for your own fields, usable as {meta.key} placeholders (see below). |
isActive |
boolean | No | Defaults to true. Disabled links show a blocked message. |
maxTotalInteractions |
integer or null | No | Lifetime cap on interactions. null = unlimited. |
maxInteractionsPerWindow |
integer or null | No | Cap on interactions within a rolling window. |
windowSeconds |
integer or null | No | Length of the rolling window, in seconds. Pairs with maxInteractionsPerWindow. |
injectionMode |
string | No | "append" (default) adds to the base instructions; "replace" overrides them. |
injectionInstructions |
string | No | Personalized instructions for this recipient. Supports placeholders (see below). Limit depends on injectionMode: append ≤ 500 words, replace ≤ 7500 characters. |
disabledMessage |
string | No | Message shown when the recipient is disabled or its total limit is reached. Max 2000 chars. |
cooldownMessage |
string | No | Message shown when the per-window limit is reached. Max 2000 chars. |
Metadata
Section titled “Metadata”metadata holds up to 24 keys. Keys are up to 64 characters of letters, numbers, underscore and hyphen. Values are strings of up to 500 characters; numbers and booleans are accepted and stored as strings. Nested objects, arrays and null values are rejected. Metadata is returned on the recipient and is set at creation; it is not one of the fields PATCH accepts.
Placeholders in injectionInstructions
Section titled “Placeholders in injectionInstructions”injectionInstructions supports placeholders that are resolved per recipient at create/update time and stored already-substituted:
{email}or{external_id}→ the recipient’sexternalId{label},{name}or{recipient}→ the recipient’slabel{meta.key}→ the value ofkeyin the recipient’smetadata
This makes a single template unique per row — ideal for batch imports. For example, "My name is {label}." becomes "My name is Jane Doe." for a recipient labelled Jane Doe. Placeholders are case-insensitive; unknown placeholders are left untouched, and a missing value resolves to an empty string.
Injection length limits
Section titled “Injection length limits”The limit is mode-dependent, because append text layers on top of the assistant’s full base prompt while replace swaps the custom-instructions block:
injectionMode: "append"— up to 500 words.injectionMode: "replace"— up to 7500 characters.
In both modes the assistant’s underlying base prompt (persona, safety, knowledge retrieval) still applies — replace only swaps the custom-instructions block, not the entire system prompt.
Idempotent batches with externalId
Section titled “Idempotent batches with externalId”externalId is unique per campaign. Re-creating a recipient with an existing externalId in the same campaign is reported as "duplicate" in results. The same externalId can be reused across different campaigns. Recipients created without an externalId are never deduplicated.
Batch create
Section titled “Batch create”Send several recipients in one items array (up to 200 per request). The shared campaignId applies to all items.
curl -X POST https://your-ragtime-instance.com/api/v1/projects/{projectId}/recipients \ -H "Authorization: Bearer rt_live_YOUR_KEY_HERE" \ -H "Content-Type: application/json" \ -d '{ "campaignId": "c1a2b3...", "items": [ { "externalId": "jane@northwind.example", "label": "Jane Doe" }, { "externalId": "john@northwind.example", "label": "John Roe", "maxTotalInteractions": 3 } ] }'// 201 Created (or 422 if every item failed){ "created": 1, "failed": 1, "total": 2, "results": [ { "status": "duplicate", "externalId": "jane@northwind.example" }, { "status": "created", "id": "...", "externalId": "john@northwind.example", "link": "https://.../i/...", ... } ]}List recipients
Section titled “List recipients”Returns recipients in a campaign with their current state and limits. Paginated. campaignId is required.
curl "https://your-ragtime-instance.com/api/v1/projects/{projectId}/recipients?campaignId=c1a2b3...&limit=50&offset=0&active=true" \ -H "Authorization: Bearer rt_live_YOUR_KEY_HERE"{ "projectId": "...", "campaignId": "c1a2b3...", "total": 1280, "limit": 50, "offset": 0, "allowance": { "used": 420, "included": 1000, "remaining": 580, "exhausted": false }, "recipients": [ { "id": "...", "campaignId": "c1a2b3...", "externalId": "jane@northwind.example", "label": "Jane Doe", "isActive": true, "link": "https://your-ragtime-instance.com/i/...", "interactionCount": 2, "maxTotalInteractions": 5, "maxInteractionsPerWindow": 1, "windowSeconds": 86400, "lastInteractionAt": "2026-06-28T14:11:00Z", "createdAt": "2026-06-20T09:00:00Z" } ]}| Query param | Description |
|---|---|
campaignId |
Required. The campaign whose recipients to list. |
limit |
Page size (1–200, default 50). |
offset |
Number of rows to skip. |
active |
true or false to filter by active state. |
total and allowance reflect the campaign, so you can paginate with limit/offset.
Get a single recipient
Section titled “Get a single recipient”curl https://your-ragtime-instance.com/api/v1/projects/{projectId}/recipients/{recipientId} \ -H "Authorization: Bearer rt_live_YOUR_KEY_HERE"Returns the same object shape as the list entries. Responds 404 if the recipient is not in this project.
Update a recipient
Section titled “Update a recipient”PATCH any subset of: label, isActive, injectionMode, injectionInstructions, disabledMessage, cooldownMessage, maxTotalInteractions, maxInteractionsPerWindow, windowSeconds. Send null to clear a nullable field.
curl -X PATCH https://your-ragtime-instance.com/api/v1/projects/{projectId}/recipients/{recipientId} \ -H "Authorization: Bearer rt_live_YOUR_KEY_HERE" \ -H "Content-Type: application/json" \ -d '{ "isActive": false, "maxTotalInteractions": 10 }'The token/link, campaignId, externalId, and interactionCount are immutable.
Delete a recipient
Section titled “Delete a recipient”curl -X DELETE https://your-ragtime-instance.com/api/v1/projects/{projectId}/recipients/{recipientId} \ -H "Authorization: Bearer rt_live_YOUR_KEY_HERE"
# 200 OK{ "success": true, "id": "a1b2c3d4-..." }Bulk delete
Section titled “Bulk delete”Delete a list of recipients, or all of them for the project. This endpoint is project-wide (it spans every campaign), so use the ids form to stay within one campaign.
# By id (max 500 per request)curl -X POST https://your-ragtime-instance.com/api/v1/projects/{projectId}/recipients/bulk-delete \ -H "Authorization: Bearer rt_live_YOUR_KEY_HERE" \ -H "Content-Type: application/json" \ -d '{ "ids": ["id-1", "id-2", "id-3"] }'
# Everythingcurl -X POST https://your-ragtime-instance.com/api/v1/projects/{projectId}/recipients/bulk-delete \ -H "Authorization: Bearer rt_live_YOUR_KEY_HERE" \ -H "Content-Type: application/json" \ -d '{ "all": true }'{ "success": true, "deleted": 3, "ids": ["id-1", "id-2", "id-3"] }The recipient experience
Section titled “The recipient experience”When a customer opens their /i/<token> link:
- The recipient’s campaign must be active (within its start/end window and not halted). Links for draft, upcoming, or completed campaigns show a neutral “currently unavailable” message.
- The campaign’s device rules apply next (active state, IP allow-list).
- The recipient’s instructions are merged into the assistant’s system prompt server-side. Because they are stored by you (the operator), they are trusted and applied regardless of the persona —
appendadds to the base instructions,replaceoverrides them. This differs from Chat Injection, where the embedding page supplies instructions at runtime and is gated by deployment settings. - The recipient’s limits are enforced.
How interactions are counted
Section titled “How interactions are counted”One interaction is one conversation (session). Continuing a conversation within the same page load does not consume another interaction — reloading the link starts a new one.
maxTotalInteractions— once reached, the link is blocked and showsdisabledMessage.maxInteractionsPerWindow+windowSeconds— limits how often a recipient can start a new conversation (e.g. once per 24h). When exceeded, the link showscooldownMessageand tells them to come back later.
interactionCount and lastInteractionAt on each recipient reflect live usage.
Campaign recipient limit
Section titled “Campaign recipient limit”Each campaign can set an optional cap on how many recipient links it holds. A request that would take the campaign past the cap is refused as a whole with 409, and details says how much room is left:
{ "code": "conflict", "error": "This batch would exceed the campaign's recipient limit.", "retryable": false, "hint": "The campaign allows 2000 recipients and has 1990; send at most 10 more.", "details": { "cap": 2000, "existing": 1990, "requested": 25 }}The cap is set per campaign in Dashboard → Deployment → Campaigns. It limits how many recipient links a campaign can hold at once; it is an operational guardrail, not a billing meter.
Interaction allowance
Section titled “Interaction allowance”Conversations through recipient links are metered against the campaign’s budget — a rolling bucket of conversations (the “taxi-meter” / pay-per-conversation mode). Each new conversation (session) consumes one unit. The counter only ever increases: deleting recipients does not refund usage.
- The budget is set per campaign by an org admin or project owner in Dashboard → Deployment → Campaigns, and extended with top-offs.
- A fixed-price (buffet) campaign is uncapped instead — usage is kept in check by the per-recipient limits, and conversations are prepaid at
$0each. - When a metered budget is used up, new conversations are hard-stopped — the link shows a neutral “currently unavailable” message until the budget is increased. In-progress conversations are unaffected.
- A metered campaign with no cap set is treated as unlimited.
The list endpoint reports the current budget state for the campaign so integrations can detect when it’s used up:
{ "projectId": "...", "campaignId": "c1a2b3...", "total": 1280, "allowance": { "used": 1280, "included": 1280, "remaining": 0, "exhausted": true }, "recipients": [ ... ]}When exhausted is true, new conversations on that campaign’s recipient links return 403 until the budget is raised.
Conversation pricing
Section titled “Conversation pricing”Each recipient conversation is billed at the campaign’s price per conversation. This price is managed in the dashboard (Deployment → Campaigns), not through the API.
- The price is set per campaign. A metered campaign with no price set bills recipient conversations at no charge.
- The price is captured on each budget top-off, so past budget changes keep the price they were made at even if you change it later (visible in the campaign’s budget history and CSV export).
- The recipient limit (how many links a campaign can hold) is separate from the conversation budget and price.
- A fixed-price (buffet) campaign bills a flat agreed total in the month it is launched instead; its per-conversation price is
$0.
Pricing is not exposed in the v1 API: budgets, prices, schedules, and limits are configured in the dashboard, while the API creates and manages the recipient links themselves.
Errors
Section titled “Errors”Errors use the standard v1 shape, with a stable code. A campaign that is no longer editable returns 409 with details.reason set to not_editable.
| Status | Code | Meaning |
|---|---|---|
200 |
Success (list, get, update, delete, bulk delete). | |
201 |
At least one recipient created. | |
400 |
invalid_request |
Invalid request (bad or unknown field, missing campaignId, batch too large, or on update, instructions that are too long or refused by the safety check). |
404 |
not_found |
Recipient or campaign not found in this project. |
409 |
conflict |
Campaign not editable, or its recipient cap would be exceeded. |
422 |
Create request where every item failed. The body is the normal {created, failed, total, results} response. |
Authentication, scope, project and rate-limit refusals work the same on every endpoint. Every response carries X-RateLimit-* headers. See Errors and Rate limits.
Dashboard
Section titled “Dashboard”You can also manage everything without the API in Dashboard → Deployment → Campaigns: create campaigns (choosing device, schedule, and budget mode), add recipients individually or in bulk, launch and halt campaigns, top off budgets, copy links, edit per-recipient instructions and limits, purge or bulk-delete recipients, and export a campaign report.