API reference/Outreach/Flows
Save a flow
Saves a flow, or updates it: the same key again changes what you send and keeps what you leave out, and steps replaces all steps. To start from another flow, send based_on with its key and no steps. Ready-made flows cannot be changed.
The steps. start is the main line: it runs for everyone, in order, each step its wait_days after the step before it (the first at 0). It takes any channel in any order: email (up to 5, which you send yourself), human_task (a call or another task by a person, up to 3), linkedin_like (up to 2), linkedin_message (up to 4; only to people who already are a LinkedIn connection, anyone else skips that step and the sequence goes on), inmail (up to 2; to anyone who is not a connection, while a connection gets the text as a normal message; it needs LinkedIn Premium, Sales Navigator or Recruiter on the account that sends, otherwise the sequence stops at that step with the reason) and at most one linkedin_invite (note true or false), as the last step of start. Two steps of the same channel there (two emails, two messages, two InMails) are at least 1 day apart. Without an invite there are no branches: everyone gets the steps of start in order, for example an email, a call and an email. With an invite come its branches: up to 4 linkedin_message in after_accept (the first wait_days after the invite is accepted, 0 = a few minutes; each next one at least 1 day after the previous), up to 4 in if_connected (for people who already are a connection), up to 2 inmail in not_accepted for people who did not accept (the first at least 1 day after the invite, the next at least 1 day after the previous InMail; a text and a subject per person), and exactly one withdraw_invite as the last step of not_accepted (7 to 60 days after the invite, or after the step before it). Up to 3 email steps go in each branch after it (after_accept, if_connected, not_accepted), between the other steps of that branch: such an email follows its branch, so it is only due once it is clear whether the invite was accepted (or the person already was a connection). Without LinkedIn: a flow of only email and human_task steps needs no LinkedIn account, no LinkedIn profile of the person (give their email) and no month of LinkedIn outreach. Profile visits are not available in a flow yet.
How waits count. The first step goes at the start of the sequence (0); to start later, give start_at when you start, and for someone you already emailed outside the flow, sent_steps on POST /outreach/flows/assign. Each next step of start waits its wait_days after the step before it, whatever its channel (a call 2 days after an email: wait_days 2 on the call). In a branch every step waits its wait_days after the step before it in that branch: the first after the invite is accepted (after_accept), after the moment of the invite (if_connected), or after the invite was sent (not_accepted).
A change. People started from then on get the new steps; sequences that already run keep theirs. To stop those, pause their campaign (PATCH /outreach/linkedin/campaigns/{id}). The reply says how many campaigns follow the flow.
A flow is the skeleton of a sequence without its texts: which steps, on which channel, in which order and branch, after how many days. Any channel in any order: emails you send yourself, a call or another human task, and LinkedIn steps, with or without a LinkedIn invite, for example an email, a call and an email, or a call, a LinkedIn message and an email. A flow with nothing on LinkedIn (only emails and human tasks) needs no LinkedIn account, no LinkedIn profile of the person (an email address does) and no month of LinkedIn outreach (uses_linkedin). You write the texts per person when you start (POST /outreach/linkedin/sequences with flow and texts, or start_flow on the assistant server), and the sequence then has exactly the steps of the flow. There are ready-made flows (invite_note_then_message, invite_then_message, invite_note_two_follow_ups, invites_only, invite_then_inmail, like_then_invite, email_then_invite, email_then_call, emails_only) and the workspace’s own, at most 100. These routes are the tools list_flows, get_flow, save_flow, delete_flow and assign_flow of the assistant server. Free.
Authentication#
Send Authorization: Bearer gtk_…. Create the key under API keys in the menu. Every gtk_ key can call these routes, also while the Signals API is closed. A missing, invalid or revoked key is 401.
Body parameters#
keystring· required — Your own name for the flow: lowercase letters, digits and_, starting with a letter, up to 40 characters (CFOs after fundingbecomescfos_after_funding). Use the same key every time.namestring— How the user sees it, up to 60 characters. Required the first time.descriptionstring | null— When to use it, up to 300 characters.nullclears it.based_onstring— A new flow: copy the steps of this flow (a ready-made one or the workspace’s own) instead of sendingsteps.stepsobject[]— The steps (see above); replaces all steps. Required the first time unlessbased_onis given.steps[].keystring· required — Your name for the step, used for its text (texts.message_1): lowercase letters, digits and_, up to 30 characters, unique in the flow.steps[].typestring· required —email(one you send yourself, instart(up to 5) or in a branch after the invite (up to 3 per branch)),linkedin_invite(at most one, the last step ofstart),linkedin_message(instartonly to existing connections; or inafter_acceptandif_connected),inmail(instart, or for people who did not accept, before the withdraw),withdraw_invite(with an invite: the last step ofnot_accepted),human_task(a person on the team calls, records a voice note, follows up, reviews or researches; up to 3 per branch; the sequence waits until the task has an outcome) orlinkedin_like(mySDR likes the lead's latest post from the last 30 days, without a text; skipped if there is none; in any branch, up to 2 per branch; counts against Max Post Likes/day). One of:email,linkedin_invite,linkedin_message,inmail,withdraw_invite,human_task,linkedin_like.steps[].branchstring—start,after_accept,if_connected,not_accepted, oron_reply(onlyhuman_tasksteps, each with atask.condition: they come after a positive or negative reply instead of in the sequence). Withoutbranch: without an invite every step goes instart; with one, an email, task or like listed before the invite goes instart, and anything else by its type (a message or task inafter_accept, an InMail or the withdraw innot_accepted). A LinkedIn message or InMail instartbefore an invite needsbranch: "start". One of:start,after_accept,if_connected,not_accepted,on_reply.steps[].taskobject— Only on ahuman_task, required there:kind(call,voice_note,follow_up,review,research,custom),instruction(what to do, e.g. a call script; optional: without it the step gets the standard text and rules of its place in the flow, as a new step on the Flows page does),deadline_days(overdue this many days after it comes due),owner(the id of a member, GET /team; default whoever starts the sequence),condition(positiveornegative, only inon_reply),if_no_phone(acallwithout a known number:research, the default, turns it into a research task;skipskips the step;find_phonewaits for a number: POST /person/phone at the normal price, and the task shows it once found),retry_after_days(afterno_answer, default 2),max_attempts(default 3; then the flow continues),on_positiveandon_negative(stop, the default, orcontinue).steps[].noteboolean— On the invite: with a note you write per person (true) or without (false, the default).make_defaultboolean— True: after saving, this flow becomes the default of the workspace (as Set as default on the Flows page). The first own flow already is.steps[].wait_daysinteger— Days to wait (see How waits count above). The first step ofstartgoes at the start (0); each next step ofstartwaits this many days after the step before it, and two steps of the same channel there are at least 1 day apart; in a branch, each step counts from the step before it in that branch, at least 1 day after the first (innot_acceptedalso the first: at least 1 day after the invite).
Response#
objectstring· required — Alwaysflow. One of:flow.keystring· required — The key.name / descriptionstring / string | null· required — How the user sees it.presetboolean· required — A ready-made flow of mySDR: it cannot be changed or deleted; start from it withbased_on.based_onstring | null· required — The flow it started from.archivedboolean· required — The user archived it in mySDR (under Flows): it no longer shows among their active flows.reply_handlingobject· required — What happens after a reply, besides the human tasks inon_reply; only the user sets it, under Reply handling on the Flows page:notify_positive(a positive reply posts to their Slack or Teams) andagent_closes_negative(you close a negative reply yourself: GET /outreach/replies then saysagent_closes).approval_requiredboolean· required — Whether the texts prepared for a lead in this flow must be approved before they start (the default; only the user sets it, under Approval on the Flows page). If so, a start skips anyone whose prepared texts are not approved yet (texts_not_approved), unless the call hastexts_approvedafter the user said yes;flow_texts_to_approveon GET /tables/leads lists them.stepsobject[]· required —key(the name of its text intexts),type(email,linkedin_invite,linkedin_message,inmail,withdraw_invite,human_task,linkedin_like),branch(start,after_accept,if_connected,not_accepted,on_reply),note(on the invite),wait_days,text(whether you write a text for it),sent_by(agentfor an email you send yourself,humanfor a human task, otherwisemysdr),taskon a human task, andconnections_only: trueon a LinkedIn message instart(it only goes to existing connections).channelsstring[]· required — The channels of the flow in the order they first come:email,call(a human task) andlinkedin.uses_linkedinboolean· required — The flow does something on LinkedIn (an invite, a message, an InMail or a like). False: it needs no LinkedIn account, no LinkedIn profile of the person (an email address does) and no month of LinkedIn outreach.has_inviteboolean· required — The flow has a LinkedIn invite, and so branches after it. False: everyone gets the steps ofstartin order.human_task_stepsstring[]· required — The human tasks of the flow: the sequence waits on their outcome (POST /human-tasks/{id}/complete); those inon_replycome after a reply.text_stepsstring[]· required — The step keys that need a text when you start.email_stepsstring[]· required — The emails you send yourself, instartor in a branch after the invite: their text and subject may come at the start or when the step is due.inmail_stepsstring[]· required — The InMails mySDR sends, instart(to anyone who is not a connection) or to people who did not accept: each needs a text (texts) and a subject (subjects) at the start.ready_needsobject· required — What Ready for campaign asks you to prepare with POST /outreach/flows/assign:texts(a text per step key) andsubjects(a subject per email and InMail step). An email step in a lead'ssent_stepsneeds nothing.in_wordsstring[]· required — The steps in plain sentences, to show the user.created_at / updated_at / viastring | null· required — When it was saved, and by which key (Claude); null for a ready-made one.is_defaultboolean— The default flow of the workspace: every lead without a flow of its own follows it. On GET, and after a save withmake_default.dashboard_urlstring· required — The flow in mySDR.createdboolean· required — True when this call created it.campaignsinteger· required — How many running or paused campaigns follow this flow.notestring· required — What the save means for people who already run.
Credits#
Free. This call never uses credits.
Errors#
- 400
invalid_request— Nokey, a key that is not valid or of a ready-made flow, nonameorstepsthe first time, steps that do not fit (paramnames the step), or an unknown field. - 404
not_found—based_onis not a flow of this workspace. - 409
invalid_state— The workspace has 100 flows already: delete one first. - 503
service_unavailable— Saving flows is not available in this environment yet. - 409
invalid_state— The key has no user behind it, so mySDR cannot record who did it. - 401
authentication_error— The Authorization header is missing, or the key is unknown or revoked. - 403
permission_error— API access is not part of your plan, or your account is suspended. - 429
rate_limit_error— More than 60 requests per minute, or more than 3 at the same time, on one key. Wait for the Retry-After header. - 503
service_unavailable— The API is temporarily unavailable. Retry later. - 500
internal_error— Something went wrong on our side.
Example
curl -X POST "https://mysdr.app/api/v1/outreach/flows" \
-H "Authorization: Bearer $MYSDR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"key": "cfo_after_funding",
"name": "CFOs after funding",
"description": "For CFOs at companies that just raised.",
"based_on": "invite_note_two_follow_ups"
}'{
"object": "flow",
"key": "cfo_after_funding",
"name": "CFOs after funding",
"description": "For CFOs at companies that just raised.",
"preset": false,
"based_on": "invite_note_two_follow_ups",
"steps": [
{
"key": "invite",
"type": "linkedin_invite",
"branch": "start",
"note": true,
"wait_days": 0,
"text": true,
"subject": false,
"sent_by": "mysdr"
},
{
"key": "message_1",
"type": "linkedin_message",
"branch": "after_accept",
"wait_days": 0,
"text": true,
"subject": false,
"sent_by": "mysdr"
},
{
"key": "follow_up_1",
"type": "linkedin_message",
"branch": "after_accept",
"wait_days": 3,
"text": true,
"subject": false,
"sent_by": "mysdr"
},
{
"key": "connected_1",
"type": "linkedin_message",
"branch": "if_connected",
"wait_days": 0,
"text": true,
"subject": false,
"sent_by": "mysdr"
},
{
"key": "withdraw",
"type": "withdraw_invite",
"branch": "not_accepted",
"wait_days": 16,
"text": false,
"subject": false,
"sent_by": "mysdr"
}
],
"channels": [
"linkedin"
],
"uses_linkedin": true,
"has_invite": true,
"text_steps": [
"invite",
"message_1",
"follow_up_1",
"connected_1"
],
"email_steps": [],
"inmail_steps": [],
"human_task_steps": [],
"ready_needs": {
"texts": [
"invite",
"message_1",
"follow_up_1",
"connected_1"
],
"subjects": []
},
"in_words": [
"invite: an invite with a note.",
"message_1: a message right after the invite is accepted.",
"follow_up_1: a message 3 days after the previous step.",
"connected_1: for an existing connection, a message at the start.",
"withdraw: not accepted after 16 days, the invite is withdrawn and the sequence ends.",
"An email in a branch follows that branch: it only goes out once it is clear whether the invite was accepted.",
"A reply stops the sequence for that person."
],
"created_at": "2026-10-28T08:00:00.000Z",
"updated_at": "2026-10-28T08:00:00.000Z",
"via": "Claude",
"archived": false,
"reply_handling": {
"notify_positive": false,
"agent_closes_negative": false
},
"approval_required": true,
"dashboard_url": "https://mysdr.app/en/gtm/flows?flow=cfo_after_funding",
"created": true,
"campaigns": 0,
"note": "Saved. Start people with it: start_flow (POST /outreach/linkedin/sequences) with flow and the texts per step key (texts)."
}