API reference/Outreach/Flows

.md

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.

POSThttps://mysdr.app/api/v1/outreach/flows

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#

  • key string · required — Your own name for the flow: lowercase letters, digits and _, starting with a letter, up to 40 characters (CFOs after funding becomes cfos_after_funding). Use the same key every time.
  • name string — How the user sees it, up to 60 characters. Required the first time.
  • description string | null — When to use it, up to 300 characters. null clears it.
  • based_on string — A new flow: copy the steps of this flow (a ready-made one or the workspace’s own) instead of sending steps.
  • steps object[] — The steps (see above); replaces all steps. Required the first time unless based_on is given.
  • steps[].key string · 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[].type string · required — email (one you send yourself, in start (up to 5) or in a branch after the invite (up to 3 per branch)), linkedin_invite (at most one, the last step of start), linkedin_message (in start only to existing connections; or in after_accept and if_connected), inmail (in start, or for people who did not accept, before the withdraw), withdraw_invite (with an invite: the last step of not_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) or linkedin_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[].branch string — start, after_accept, if_connected, not_accepted, or on_reply (only human_task steps, each with a task.condition: they come after a positive or negative reply instead of in the sequence). Without branch: without an invite every step goes in start; with one, an email, task or like listed before the invite goes in start, and anything else by its type (a message or task in after_accept, an InMail or the withdraw in not_accepted). A LinkedIn message or InMail in start before an invite needs branch: "start". One of: start, after_accept, if_connected, not_accepted, on_reply.
  • steps[].task object — Only on a human_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 (positive or negative, only in on_reply), if_no_phone (a call without a known number: research, the default, turns it into a research task; skip skips the step; find_phone waits for a number: POST /person/phone at the normal price, and the task shows it once found), retry_after_days (after no_answer, default 2), max_attempts (default 3; then the flow continues), on_positive and on_negative (stop, the default, or continue).
  • steps[].note boolean — On the invite: with a note you write per person (true) or without (false, the default).
  • make_default boolean — 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_days integer — Days to wait (see How waits count above). The first step of start goes at the start (0); each next step of start waits 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 (in not_accepted also the first: at least 1 day after the invite).

Response#

  • object string · required — Always flow. One of: flow.
  • key string · required — The key.
  • name / description string / string | null · required — How the user sees it.
  • preset boolean · required — A ready-made flow of mySDR: it cannot be changed or deleted; start from it with based_on.
  • based_on string | null · required — The flow it started from.
  • archived boolean · required — The user archived it in mySDR (under Flows): it no longer shows among their active flows.
  • reply_handling object · required — What happens after a reply, besides the human tasks in on_reply; only the user sets it, under Reply handling on the Flows page: notify_positive (a positive reply posts to their Slack or Teams) and agent_closes_negative (you close a negative reply yourself: GET /outreach/replies then says agent_closes).
  • approval_required boolean · 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 has texts_approved after the user said yes; flow_texts_to_approve on GET /tables/leads lists them.
  • steps object[] · required — key (the name of its text in texts), 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 (agent for an email you send yourself, human for a human task, otherwise mysdr), task on a human task, and connections_only: true on a LinkedIn message in start (it only goes to existing connections).
  • channels string[] · required — The channels of the flow in the order they first come: email, call (a human task) and linkedin.
  • uses_linkedin boolean · 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_invite boolean · required — The flow has a LinkedIn invite, and so branches after it. False: everyone gets the steps of start in order.
  • human_task_steps string[] · required — The human tasks of the flow: the sequence waits on their outcome (POST /human-tasks/{id}/complete); those in on_reply come after a reply.
  • text_steps string[] · required — The step keys that need a text when you start.
  • email_steps string[] · required — The emails you send yourself, in start or in a branch after the invite: their text and subject may come at the start or when the step is due.
  • inmail_steps string[] · required — The InMails mySDR sends, in start (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_needs object · required — What Ready for campaign asks you to prepare with POST /outreach/flows/assign: texts (a text per step key) and subjects (a subject per email and InMail step). An email step in a lead's sent_steps needs nothing.
  • in_words string[] · required — The steps in plain sentences, to show the user.
  • created_at / updated_at / via string | null · required — When it was saved, and by which key (Claude); null for a ready-made one.
  • is_default boolean — The default flow of the workspace: every lead without a flow of its own follows it. On GET, and after a save with make_default.
  • dashboard_url string · required — The flow in mySDR.
  • created boolean · required — True when this call created it.
  • campaigns integer · required — How many running or paused campaigns follow this flow.
  • note string · required — What the save means for people who already run.

Credits#

Free. This call never uses credits.

Errors#

  • 400 invalid_request — No key, a key that is not valid or of a ready-made flow, no name or steps the first time, steps that do not fit (param names the step), or an unknown field.
  • 404 not_found — based_on is 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

POST/outreach/flows
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"
  }'
Response
{
  "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)."
}