> ## Documentation Index
> Fetch the complete documentation index at: https://docs.oneshotagent.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Send a Reply

> Send a message in an existing LinkedIn conversation through a connected account that granted reply.

Sends one message into an existing conversation from the human's account, then returns `processing` with a request to poll. Paid: flat price per message (see [Pricing](/pricing)). Requires the `reply` grant and an `Idempotency-Key` header.

<Warning>
  The human's account sends the message, and a message LinkedIn accepted **cannot be recalled**. Every call must carry an `Idempotency-Key`. The SDKs generate one if you omit it and reuse it on retries.
</Warning>

<Snippet file="audit-context-params.mdx" />

## Request

<ParamField header="Idempotency-Key" type="string" required>
  1–255 chars of `[A-Za-z0-9_.:-]`. A retry with the same key and body replays the accepted response instead of paying and sending again. The same key with a different body returns `422 idempotency_key_reuse`. The key is also stored on the action, so reuse is refused (`409 duplicate_request`, with the original `request_id`) even if the cache is unavailable.
</ParamField>

<ParamField body="account_id" type="string" required>Connected LinkedIn account id.</ParamField>
<ParamField body="conversation_id" type="string" required>Conversation id from [Conversations](/api-reference/linkedin/conversations). To reach a connection you have no conversation with yet, use [Message a connection](/api-reference/linkedin/message).</ParamField>
<ParamField body="text" type="string" required>1–4000 characters.</ParamField>

## Before payment

Every rejection happens before payment. Each of these returns a non-2xx and is never settled:

| Status | `error` | Why |
| - | - | - |
| 400 | `idempotency_key_required` | No `Idempotency-Key` header. |
| 400 | `invalid_request` | Body failed validation. |
| 400 | `exceeds_caller_budget` | Price above your `X-Max-Cost-USDC`. |
| 404 | `account_not_found` / `conversation_not_found` | Not yours, or not synced. |
| 403 | `grant_denied` / `grant_revoked` | The human did not grant `reply`, or revoked. |
| 409 | `account_not_ready` | Account is `reconnect_required` / `error`. |
| 409 | `conversation_read_only` | LinkedIn marks the thread read-only. |
| 400 | `content_blocked` | Content safety. |
| 429 | `account_send_limit` | Daily cap for this account reached (`limit`, `used`, `pending`, `resets_at` in `details`). |
| 503 | `limits_unavailable` | Caps could not be verified, so the request fails closed. Retry shortly. |

## Response

```json theme={null}
{
  "request_id": "…", "receipt_id": "rcpt_…", "status": "processing", "tool": "linkedin",
  "linkedin": {
    "action_request_id": "…", "action": "reply", "account_id": "…", "conversation_id": "…",
    "scheduled_for": "2026-09-15T12:04:41.000Z",
    "headroom": { "limit": 50, "used": 3, "pending": 1, "resets_at": "2026-09-16T00:00:00.000Z" }
  }
}
```

`scheduled_for` is non-null when a spacing delay was scheduled at acceptance (randomized, \~45 s ± 50 % between sends on the same account). `null` means no initial delay. The worker can still defer the send during its execution-time checks. The response is `processing` either way.

## Outcome

Poll `GET /v1/requests/{request_id}` (the SDK waits for you):

* **completed**: `{ "action_request_id", "status": "sent", "provider_message_id", "message_id", "sent_at" }`. The message is also visible in [Messages](/api-reference/linkedin/messages) with `source: "send"`.
* **failed**: `error_code` ∈ `grant_revoked` (grant changed or revoked since enqueue), `account_disconnected` (LinkedIn refused the session, and the account is now `reconnect_required`), `recipient_not_connected`, `content_rejected`, `rate_limited`, `account_send_limit`, `target_not_found`. The failed-job sweep refunds jobs that failed before sending.
* **processing with `send_status: "ambiguous"`**: the upstream timed out after the request may have been delivered. OneShot does **not** retry blindly. It reconciles against the chat every few minutes and re-attempts once if there is still no evidence after 10 minutes. After 24 hours without confirmation it fails the job with `send_unverified`. An unconfirmed send is never auto-refunded.

## Execution-time checks

A queued request has not been sent yet. Immediately before the upstream call, the worker re-checks under a row lock that the account is `connected`, not revoked, and still grants `reply`, and that `grant_version` has not changed since acceptance. It re-reserves a slot against the daily cap (Redis with a database fallback; the check fails closed). If sending now would break the spacing, it defers the send instead of sleeping. A request that fails these checks is cancelled, its job fails with the matching code, and nothing reaches LinkedIn.

## Example

```typescript theme={null}
const result = await agent.linkedinReply({
  accountId, conversationId, text: 'Tuesday 3pm works — sending an invite.',
  memo: 'reply to inbound from Sam Rivera', decisionContext: { goalId: 'cadence:acme' },
});
console.log(result.status, result.provider_message_id);
```

```python theme={null}
result = client.linkedin_reply(account_id, conversation_id, "Tuesday 3pm works — sending an invite.", memo="reply to Sam")
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.