Send a Reply
curl --request POST \
--url https://win.oneshotagent.com/v1/tools/linkedin/reply \
--header 'Content-Type: application/json' \
--header 'Idempotency-Key: <idempotency-key>' \
--data '
{
"account_id": "<string>",
"conversation_id": "<string>",
"text": "<string>"
}
'import requests
url = "https://win.oneshotagent.com/v1/tools/linkedin/reply"
payload = {
"account_id": "<string>",
"conversation_id": "<string>",
"text": "<string>"
}
headers = {
"Idempotency-Key": "<idempotency-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Idempotency-Key': '<idempotency-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({account_id: '<string>', conversation_id: '<string>', text: '<string>'})
};
fetch('https://win.oneshotagent.com/v1/tools/linkedin/reply', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://win.oneshotagent.com/v1/tools/linkedin/reply",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'account_id' => '<string>',
'conversation_id' => '<string>',
'text' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"Idempotency-Key: <idempotency-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://win.oneshotagent.com/v1/tools/linkedin/reply"
payload := strings.NewReader("{\n \"account_id\": \"<string>\",\n \"conversation_id\": \"<string>\",\n \"text\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Idempotency-Key", "<idempotency-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://win.oneshotagent.com/v1/tools/linkedin/reply")
.header("Idempotency-Key", "<idempotency-key>")
.header("Content-Type", "application/json")
.body("{\n \"account_id\": \"<string>\",\n \"conversation_id\": \"<string>\",\n \"text\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://win.oneshotagent.com/v1/tools/linkedin/reply")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Idempotency-Key"] = '<idempotency-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"account_id\": \"<string>\",\n \"conversation_id\": \"<string>\",\n \"text\": \"<string>\"\n}"
response = http.request(request)
puts response.read_bodyLinkedIn
Send a Reply
Send a message in an existing LinkedIn conversation through a connected account that granted reply.
POST
/
v1
/
tools
/
linkedin
/
reply
Send a Reply
curl --request POST \
--url https://win.oneshotagent.com/v1/tools/linkedin/reply \
--header 'Content-Type: application/json' \
--header 'Idempotency-Key: <idempotency-key>' \
--data '
{
"account_id": "<string>",
"conversation_id": "<string>",
"text": "<string>"
}
'import requests
url = "https://win.oneshotagent.com/v1/tools/linkedin/reply"
payload = {
"account_id": "<string>",
"conversation_id": "<string>",
"text": "<string>"
}
headers = {
"Idempotency-Key": "<idempotency-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Idempotency-Key': '<idempotency-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({account_id: '<string>', conversation_id: '<string>', text: '<string>'})
};
fetch('https://win.oneshotagent.com/v1/tools/linkedin/reply', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://win.oneshotagent.com/v1/tools/linkedin/reply",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'account_id' => '<string>',
'conversation_id' => '<string>',
'text' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"Idempotency-Key: <idempotency-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://win.oneshotagent.com/v1/tools/linkedin/reply"
payload := strings.NewReader("{\n \"account_id\": \"<string>\",\n \"conversation_id\": \"<string>\",\n \"text\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Idempotency-Key", "<idempotency-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://win.oneshotagent.com/v1/tools/linkedin/reply")
.header("Idempotency-Key", "<idempotency-key>")
.header("Content-Type", "application/json")
.body("{\n \"account_id\": \"<string>\",\n \"conversation_id\": \"<string>\",\n \"text\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://win.oneshotagent.com/v1/tools/linkedin/reply")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Idempotency-Key"] = '<idempotency-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"account_id\": \"<string>\",\n \"conversation_id\": \"<string>\",\n \"text\": \"<string>\"\n}"
response = http.request(request)
puts response.read_bodySends 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). Requires the reply grant and an Idempotency-Key header.
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.Request
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.string
required
Connected LinkedIn account id.
string
required
Conversation id from Conversations. To reach a connection you have no conversation with yet, use Message a connection.
string
required
1–4000 characters.
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
{
"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
PollGET /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 withsource: "send". - failed:
error_code∈grant_revoked(grant changed or revoked since enqueue),account_disconnected(LinkedIn refused the session, and the account is nowreconnect_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 withsend_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 isconnected, 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
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);
result = client.linkedin_reply(account_id, conversation_id, "Tuesday 3pm works — sending an invite.", memo="reply to Sam")