# Send a LinkedIn reply

Sends your reply right away in the LinkedIn conversation where the person last replied to you, from the LinkedIn account of the user behind this key. The person is `lead_id` (a lead from the signals) or `linkedin_url` (someone you reached yourself): both are in GET /outreach/replies. Write the text yourself, in full and in their language: it goes out exactly as written, so a placeholder is refused. mySDR does the LinkedIn part of your outreach: you pick the people and write every text, mySDR sends it from the user's own LinkedIn account within their working hours and the SafeOutreach limits, notices accepted invites and replies, and stops a person's sequence when they reply. These routes are the tools `start_linkedin_sequence`, `get_outreach_status`, `get_outreach_events`, `get_replies`, `send_reply` and `stop_linkedin_sequence` of the assistant server, with the same bodies; your own emails you log with POST /outreach/activity. Free: no credits, except a month of unlimited LinkedIn outreach per account (750 credits, within the SafeOutreach limits; POST /outreach/linkedin/sequences).

**There is no approval step in mySDR**: the reply goes out at once. Show the user the text first and send only after they said yes.

A reply uses no daily limit and no credits; a pause of the organization or of the LinkedIn account holds it back. It also works when the person is marked not interested (a polite close). LinkedIn only: a reply by email you send with your own tools and log with POST /outreach/activity.

## 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

- **`lead_id`** `integer` — A lead from the signals: `lead_id` from GET /outreach/replies (`201`, or as text `"201"`). Or:
- **`linkedin_url`** `string` — The `linkedin_url` from GET /outreach/replies, for someone who is not a lead from the signals.
- **`text`** `string` · **required** — The reply exactly as it goes out, at most 8000 characters.
- **`image_url`** `string` — An image that goes out with the text: a direct https link to a PNG, JPEG, GIF or WebP file of at most 5 MB. mySDR fetches it first, so a link that gives no image sends nothing. A share page (Google Drive, Dropbox without a download link) is a web page, not an image. Show the user the image too before they say yes.
- **`reason`** `string` — Why this reply, in one sentence (up to 500 characters); shown with the conversation in mySDR.

## Response

- **`status`** `string` · **required** — `sent`, or `approved` while it is still going out. One of: `sent`, `approved`.
- **`sent_at`** `string | null` · **required** — When it went out.
- **`image_url`** `string | null` · **required** — The image that went with it, or null.
- **`reply_to`** `object` · **required** — Their message you answered: `untrusted_text` (their words) and `at`.
- **`note`** `string` · **required** — The same in a sentence.
- **`dashboard_url`** `string` · **required** — The Inbox in mySDR.

## Credits

Free. A reply never uses credits or a daily limit.

## Errors

- **400** `invalid_request` — An unknown field, neither or both of `lead_id` and `linkedin_url`, a `lead_id` that is not a number, an empty text, a text that is too long or still has placeholders, a `linkedin_url` that is not a profile, or an `image_url` that is not an https link to an image (the message says why). Nothing was sent.
- **404** `not_found` — No lead from the signals has this `lead_id`.
- **409** `invalid_state` — The person has not replied in a LinkedIn conversation of this user (for a first message: POST /outreach/linkedin/sequences), replied by email, the conversation was archived or its flow cancelled, LinkedIn is not connected, outreach is paused, or the lead has no LinkedIn profile.
- **502** `upstream_error` — LinkedIn did not accept the reply, or did not answer while sending: in that case it is not certain whether it went out, so check `answered` in GET /outreach/replies before you send it again. Or the host of `image_url` did not answer in time (`param` `image_url`): nothing was sent, try again in a moment.
- **409** `invalid_state` — The key has no user behind it, so there is no LinkedIn account to send from.
- **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/linkedin/replies" \
  -H "Authorization: Bearer $MYSDR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "lead_id": 201,
    "text": "Hi Anna, happy to explain. Does Thursday at 10:00 work for a short call?",
    "reason": "She asked what it costs."
  }'
```

### JavaScript

```javascript
const response = await fetch('https://mysdr.app/api/v1/outreach/linkedin/replies', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.MYSDR_API_KEY}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    "lead_id": 201,
    "text": "Hi Anna, happy to explain. Does Thursday at 10:00 work for a short call?",
    "reason": "She asked what it costs."
  })
});
const body = await response.json();
```

### Python

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

payload = {
  "lead_id": 201,
  "text": "Hi Anna, happy to explain. Does Thursday at 10:00 work for a short call?",
  "reason": "She asked what it costs."
}

request = urllib.request.Request(
    "https://mysdr.app/api/v1/outreach/linkedin/replies",
    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
{
  "status": "sent",
  "sent_at": "2026-10-24T09:03:00.000Z",
  "image_url": null,
  "reply_to": {
    "untrusted_text": "Sounds interesting. What does it cost?",
    "at": "2026-10-24T07:41:00.000Z"
  },
  "note": "Sent in the conversation.",
  "dashboard_url": "https://mysdr.app/en/outreach"
}
```
