/v1/campaigns endpoints, and you enroll people in two calls: prepare, then start. Nothing is enrolled or sent until you start.
Permissions
API keys need these permissions, which an admin sets in Settings > API access:
Only the API key or person who prepared an enrollment batch can change, start or discard it. Anyone with campaign access can read it or stop the people it enrolled.
An API key sends as a team member you name in
sender_team_member_id. That person needs access to campaigns, and the mailbox in from_email must be theirs or shared with them. GET /v1/campaigns/mailboxes?sender_team_member_id=... lists the mailboxes they can send from.
1. Prepare
Each person not ready has
issues with a code and a message you can show to a user. Warnings such as enrolled_in_other_campaign, previously_in_campaign (they were in this campaign before and were stopped) or likely_bounce don’t stop anyone. Each person also lists the overrides they are enrolled despite, with the override_reason.
A batch holds up to your workspace’s recipient limit (50 unless your admin changed it, and never more than 1,000), and each person once. A person listed twice or not in your workspace fails the whole request with 400 VALIDATION_ERROR.
2. Check and fix
include=content to see every person’s final steps.
Fix each person who needs it:
{{variables}}. Every change raises the batch version. Changes to different people can be sent at the same time, but send all changes to one person in one request; adding people returns 409 BATCH_VERSION_CHANGED if another change lands first, so read the batch and add them again. An email that isn’t on the person’s profile needs add_email_to_person: true; a batch can’t send to an address without saving it.
To add more people to the same draft, send them with the same fields as recipients when you prepared it:
POST /v1/campaigns/{id}/enrollment-batches/{batch_id}/discard.
3. Start
version and summary.ready you read. If either changed, you get 409 and nobody is enrolled, so you never send something you haven’t seen. Start uses exactly the emails stored on the batch; if the campaign’s steps changed after you prepared, start returns 409 BATCH_STALE and you prepare a new batch.
Everyone is checked again at start. Someone who unsubscribed or was enrolled in this campaign by someone else in the meantime is listed in rejected_at_start with the reason, and everyone else is enrolled. follow_up_errors lists people who were enrolled but whose follow-up didn’t finish: adding them to the job (retried automatically), saving their email as preferred, or adding them as a client contact.
Calling start again for the same batch is safe: it returns the first result with replayed: true, and finishes anything that was interrupted. To undo a start, call POST /v1/campaigns/{id}/enrollments/stop with { "batch_id": "..." }.
Variables
GET /v1/campaigns/variables lists the {{variables}} steps can use. For each person, a variable is filled from, in order:
- The person’s own
variable_values - The batch’s
variable_values - The automatic value: the person’s record (name, title, company), the sender, or the job in
job_id({{search_name}},{{search_client}},{{application_link}})
blank_variables leaves a variable out and tidies the spacing around it. When you change one person, the latest instruction wins: leaving a variable out drops the value you gave them, and giving a value takes it off their blank list. An empty automatic value (a person with no company, a confidential job’s client) needs a value or a blank.
{{ai:name}} variables are written by AI for each person from their profile, notes, emails and the job. While they are written the batch is preparing; read it again until it is ready. To write one yourself, pass it in variable_values as "ai:name".
When the first email goes out
Sending windows, weekends and holidays are read in each step’s
timezone; with scheduled, a first step without a timezone uses start_timezone. Holidays come from your workspace’s holiday calendar, and send_on_holidays: true sends on them anyway. A mailbox’s daily limit counts days in its sending_timezone, so a large batch spreads its first emails over several days.
Each ready person has a first_email_estimated_at. With now and scheduled the first email isn’t moved into sending hours or off a weekend or holiday, so the batch warns with outside_sending_window when it would go out off schedule. With next_window the same warning tells you the first email has to wait, and until when:
reason is outside_hours, weekend or holiday. Show the message before you start, the way the Stardex enroller does.
A batch’s start_mode and start_at can’t be changed after you prepare it. To move the first email, discard the batch and prepare a new one with the same people and values. A scheduled batch must be started before start_at, or start returns 409 START_TIME_PASSED.
Limits
With API keys, MCP and Ask Dex together, a workspace can enroll up to 500 people in 24 hours by default. Signed-in users calling the API and enrollments made in the Stardex app don’t count toward it. Over that, prepare and start return429 DAILY_ENROLLMENT_LIMIT_REACHED with limit, used, requested and resets_at. Contact support to raise it.
After enrolling
Edits to a campaign’s steps reach people enrolled afterwards. Sending window, timezone, signature and task blocking also apply to people already enrolled. A step created without a
timezone sends in the campaign owner’s mailbox timezone. If the owner has no connected mailbox the step has none, and a batch warns with steps_without_timezone because those emails go out in New York business hours.
A person’s later emails always reply in the same thread as Re: <first email subject>. Changing a person’s first subject, in a batch or after enrolling, renames their later subjects too.
To find who replied positively, search enrollees with reply_intent: "positive"; max_opens: 0 finds people who never opened, including bounced people (status: "bounced" lists those).
Campaign stats
GET /v1/campaigns/{id}, and POST /v1/campaigns/search with include: ["stats"], return the numbers on the campaign card:
Bounced emails never reached an inbox, so they’re left out of rates, as on the campaign card. To find the biggest or best campaigns, search with
sort_by: "enrollments" or "reply_rate". Rates are null when nobody is enrolled, or everyone enrolled bounced.
Errors
Every error has acode and a message written for the person using your integration: