Skip to main content
POST
Add contacts to a sequence
Enrolls up to 500 people in one synchronous call. For more, start an enrollment run, which works through up to 5,000 in the background.

Identifying people

Give each prospect one of:
  • contact: the id of a contact already on your team.
  • email: matched against your team’s contacts; a new contact is created when there is no match.
  • linkedin_username: the handle, or a profile URL. Matched and created the same way.
A profile URL is reduced to the handle: https://www.linkedin.com/in/ada-lovelace/, linkedin.com/in/ada-lovelace and https://uk.linkedin.com/in/ada-lovelace?trk=... all become ada-lovelace. Anything else, such as a company page or a URL on another site, is sent on as it is. When the person already exists, the other fields you send (first_name, title, website, timezone and so on) only fill fields that are empty on the contact; they never overwrite. custom_fields are the exception: a value you send replaces the contact’s value for the same label, so {{custom.<label>}} variables render what you sent.

Phone numbers

Send phone_numbers with every number you have for the person, primary first, up to 10:
  • The numbers are added to the contact’s numbers. None are taken away, and a number the contact already has, compared on its last 10 digits, is not added twice.
  • Write number in E.164, such as +14155552671.
  • Send type (mobile, direct or office) to keep the entry as you wrote it, country_code and extension included. Without a type, the number is read again from number alone: a number starting with + keeps its country code, a leading 0 is read as a UK number, and a bare 10-digit number as US. It is filed as mobile, or office when it has an extension, any country_code or extension you sent is ignored, and a number that cannot be read is dropped.
  • phone_number (with phone_type) still works. With phone_numbers, it is added after them unless it is already listed.
  • A phone number is never used to match a person to an existing contact: colleagues who share a company line are not one person.

Example

owner_id is who ends up holding the enrollments, so whose queue their tasks land in. It defaults to user_id.

Reading the response

A 200 does not mean everyone went in. Every response reports the whole outcome:
skipped[].reason currently carries the refusal message, as above, not a reason code, and rule on a skip is an object with id, name, action and reason. The schema below still shows a code and a string. For the code, read List enrollment refusals, where every skip is recorded.

People started in another sequence

A rule of engagement with the route_to_sequence action starts some people in another sequence instead of this one. They are listed in routed, and counted apart from enrolled and skipped: they are not in this sequence, and nothing went wrong.
  • sequence_state is their enrollment in the other sequence. routed_to.existing: true means they were already live there, and sequence_state is that enrollment.
  • Someone routed into a sequence is not routed again from there.
  • When the other sequence can’t take them (adding to it is paused, it is full or archived, or they are suppressed), they land in skipped, with a reason such as Couldn't start them in "Email follow-up": it is at capacity and the rule. A sequence that is turned off still takes them; they wait there until it is turned on.
  • The refusals list records them with the reason routed. See Start them in another sequence instead.

Why someone is skipped

The whole request is refused with 422, and nobody is enrolled, when the sequence is archived, has no steps, or has throttle_paused set, or when owner_id is not on the team.

Timing

On an enabled sequence, each person’s first step is scheduled straight away: it comes due after the first step’s interval_seconds, inside the sending window. On a disabled sequence people wait as pending and start when it is enabled.

Retrying

Re-sending someone who is already in the sequence skips them as already enrolled rather than adding a duplicate, so a retry never enrolls anyone twice. Send an Idempotency-Key anyway: a retry with the same key returns the original response, including the original skipped list.

Authorizations

x-api-key
string
header
required

Team-scoped API key generated in the Salesfinity dashboard under Settings → Connections → API & Webhooks. A missing or invalid key returns HTTP 403.

Headers

Idempotency-Key
string

Makes this write safe to retry. Send a unique value, such as a UUID, and send the same value again when you retry the same request. A retry with the same key and the same request (method, path including the query string, and JSON body) returns the original successful response without acting twice. The same key with a different body or path returns 409, and so does a retry while the first request is still running; that one is safe to repeat after a moment. A request that fails frees its key, so a retry runs the write again: after a 5xx on a create, confirm with a GET before retrying. Keys are kept for 24 hours and are scoped to your team.

Path Parameters

id
string
required

24-character sequence id.

Body

application/json
user_id
string
required

The team member this call acts as. Must be an active member of the key's team with an active plan or seat. A 24-character hexadecimal ID.

Example:

"507f1f77bcf86cd799439011"

prospects
object[]
required

1 to 500 people. For more, start an enrollment run (POST /v2/sequencer/enrollment-runs).

Maximum array length: 500
owner_id
string

Who owns the resulting enrollments, and so whose queue their tasks land in. Defaults to user_id. Must be a member of the team; otherwise the whole request is refused with 422. A 24-character hexadecimal ID.

Response

The outcome for every person sent.

The outcome of adding people. A 200 does not mean everyone went in: read skipped and routed.

enrolled
integer

How many people went in, including anyone listed in held. People in routed are not counted.

skipped
object[]

Everyone refused, in the order sent. Each refusal is also recorded, with a reason code, on GET /v2/sequencer/sequences/{id}/enrollment-attempts.

held
object[]

People enrolled but held in pending until a manager approves them, because a rule requires approval. They are counted in enrolled.

warnings
object[]

People who went in although a rule matched them with a warning.

routed
object[]

People a route_to_sequence rule started in another sequence instead of this one. They are not counted in enrolled, and are not in skipped or sequence_states. Someone routed into a sequence is not routed again from there. When the other sequence cannot take them (adding to it is paused, it is full or archived, or they are suppressed), they land in skipped instead, with the reason and the rule. A sequence that is turned off still takes them; they wait there until it is turned on.

sequence_states
object[]

The enrollments created in this sequence, in the same shape as GET /v2/sequencer/enrollments/{id}, except that contact is expanded to _id, first_name, last_name, email, linkedin_username, account, title and channel_stats. On an enabled sequence their state is active; on a disabled one, or when held, it is pending.