# Assign a flow

Gives up to 50 people a flow, or takes it away with `flow: null`. They follow it from then on: a start with any other flow skips them (`other_flow`). One person is one assignment, on their LinkedIn profile whichever key you use for them, or without a profile on their email address (a flow without LinkedIn).

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

- **`flow`** `string | null` · **required** — The key of the flow, or `null` to take their flow away.
- **`leads`** `object[]` · **required** — 1 to 50 people, each with exactly one of the keys below (`email` may go with it).
- **`leads[].id`** `string` — A row of GET /tables/leads: its `id` (a number, `per_…` or `sav_…`).
- **`leads[].lead_id`** `integer` — A lead from the signals: the number GET /tables/leads gives.
- **`leads[].person_id`** `string` — A `per_…` from the Signals API, kept 30 days.
- **`leads[].linkedin_url`** `string` — A LinkedIn profile URL (`https://www.linkedin.com/in/…`).
- **`leads[].email`** `string` — Their email address. For a flow without LinkedIn (`uses_linkedin` false) it is enough on its own: someone without a LinkedIn profile follows the flow on their address.
- **`texts`** `object` — Prepared texts for everyone in the call, per step key of the flow (`text_steps` and `email_steps`), e.g. `{"invite": "Hi {{first_name}}, …", "message_1": "…"}`. A lead's own text wins. Placeholders are filled at the start.
- **`subjects`** `object` — Prepared subjects of the email steps, per step key.
- **`leads[].texts`** `object` — This person's own prepared text per step key; `null` removes one. Sending the same flow again adds or replaces texts and keeps the others; another flow drops them.
- **`leads[].subjects`** `object` — This person's own subject per email step key.
- **`sent_steps`** `object` — For everyone in the call: the emails at the front of the main line (`start`, before its first step that is not an email) that already went out, outside the flow, with when (ISO 8601; a date is fine), e.g. `{"email_1": "2026-10-27T09:15:00+01:00"}`. In order from the first (email_2 needs email_1), not in the future, at most 90 days ago. Report those emails first with POST /outreach/activity.
- **`leads[].sent_steps`** `object` — The same for this person (it wins over the shared one); `null` removes a step. The person then continues in the flow: the start skips these steps and the next one waits its `wait_days` after the last of them; they need no prepared text, and someone who only got these emails (no LinkedIn sequence, no reply) can be Ready for campaign. Another flow drops them.

## Response

- **`flow`** `object | null` · **required** — The flow (`key`, `name`), or null.
- **`assigned / unassigned`** `integer` · **required** — How many people got the flow, or lost theirs.
- **`leads`** `object[]` · **required** — Who it applies to: `index`, `lead`, and with a flow `texts_missing` (the steps without a prepared text, `email_1.subject` for a subject; empty = Ready for campaign as far as the texts go) and `sent_steps` when some already went out.
- **`texts_complete`** `integer` — With a flow: how many of these people have every text of the flow prepared.
- **`texts_cleared`** `integer` — With a flow: how many had texts prepared for another flow, which are gone now.
- **`skipped`** `object[]` · **required** — Who it could not apply to, and why: `index`, `lead`, `code`, `reason`.
- **`note`** `string` · **required** — What it means.
- **`dashboard_url`** `string` · **required** — The Leads table in mySDR.

## Credits

Free. This call never uses credits.

## Errors

- **400** `invalid_request` — No `leads`, more than the maximum, a person without a valid key, an unknown field, texts without a flow, or a text for a step the flow does not have or longer than that step takes (`param` names it).
- **404** `not_found` — There is no flow with this key.
- **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

```bash
curl -X POST "https://mysdr.app/api/v1/outreach/flows/assign" \
  -H "Authorization: Bearer $MYSDR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "flow": "cfo_after_funding",
    "texts": {
      "message_1": "Thanks for connecting, {{first_name}}!"
    },
    "leads": [
      {
        "id": "201",
        "texts": {
          "invite": "Hi {{first_name}}, congrats on the round."
        }
      },
      {
        "linkedin_url": "https://www.linkedin.com/in/jane-example"
      }
    ]
  }'
```

### JavaScript

```javascript
const response = await fetch('https://mysdr.app/api/v1/outreach/flows/assign', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.MYSDR_API_KEY}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    "flow": "cfo_after_funding",
    "texts": {
      "message_1": "Thanks for connecting, {{first_name}}!"
    },
    "leads": [
      {
        "id": "201",
        "texts": {
          "invite": "Hi {{first_name}}, congrats on the round."
        }
      },
      {
        "linkedin_url": "https://www.linkedin.com/in/jane-example"
      }
    ]
  })
});
const body = await response.json();
```

### Python

```python
import json
import os
import urllib.request

payload = {
  "flow": "cfo_after_funding",
  "texts": {
    "message_1": "Thanks for connecting, {{first_name}}!"
  },
  "leads": [
    {
      "id": "201",
      "texts": {
        "invite": "Hi {{first_name}}, congrats on the round."
      }
    },
    {
      "linkedin_url": "https://www.linkedin.com/in/jane-example"
    }
  ]
}

request = urllib.request.Request(
    "https://mysdr.app/api/v1/outreach/flows/assign",
    headers={
    "Authorization": f"Bearer {os.environ['MYSDR_API_KEY']}",
    "Content-Type": "application/json"
    },
    data=json.dumps(payload).encode(),
    method="POST",
)
with urllib.request.urlopen(request) as response:
    body = json.load(response)
```

### Example response

```json
{
  "flow": {
    "key": "cfo_after_funding",
    "name": "CFOs after funding"
  },
  "assigned": 2,
  "unassigned": 0,
  "leads": [
    {
      "index": 0,
      "lead": "201",
      "texts_missing": []
    },
    {
      "index": 1,
      "lead": "https://www.linkedin.com/in/jane-example",
      "texts_missing": [
        "invite"
      ]
    }
  ],
  "skipped": [],
  "texts_complete": 1,
  "texts_cleared": 0,
  "note": "These people follow flow cfo_after_funding from now on. A start with another flow skips them (other_flow); sequences that already run keep their steps. 1 of 2 have every text of the flow prepared; texts_missing says what the others still need (assign_flow again with texts adds them). Start them with start_flow (POST /outreach/linkedin/sequences) and flow cfo_after_funding.",
  "dashboard_url": "https://mysdr.app/en/gtm/leads?ready=1"
}
```
