Message a Connection
curl --request POST \
--url https://win.oneshotagent.com/v1/tools/linkedin/message \
--header 'Content-Type: application/json' \
--header 'Idempotency-Key: <idempotency-key>' \
--data '
{
"account_id": "<string>",
"profile": "<string>",
"text": "<string>"
}
'import requests
url = "https://win.oneshotagent.com/v1/tools/linkedin/message"
payload = {
"account_id": "<string>",
"profile": "<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>', profile: '<string>', text: '<string>'})
};
fetch('https://win.oneshotagent.com/v1/tools/linkedin/message', 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/message",
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>',
'profile' => '<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/message"
payload := strings.NewReader("{\n \"account_id\": \"<string>\",\n \"profile\": \"<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/message")
.header("Idempotency-Key", "<idempotency-key>")
.header("Content-Type", "application/json")
.body("{\n \"account_id\": \"<string>\",\n \"profile\": \"<string>\",\n \"text\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://win.oneshotagent.com/v1/tools/linkedin/message")
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 \"profile\": \"<string>\",\n \"text\": \"<string>\"\n}"
response = http.request(request)
puts response.read_bodyLinkedIn
Message a Connection
Send a message to a first-degree connection: into the conversation the account already has with them, or a new one.
POST
/
v1
/
tools
/
linkedin
/
message
Message a Connection
curl --request POST \
--url https://win.oneshotagent.com/v1/tools/linkedin/message \
--header 'Content-Type: application/json' \
--header 'Idempotency-Key: <idempotency-key>' \
--data '
{
"account_id": "<string>",
"profile": "<string>",
"text": "<string>"
}
'import requests
url = "https://win.oneshotagent.com/v1/tools/linkedin/message"
payload = {
"account_id": "<string>",
"profile": "<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>', profile: '<string>', text: '<string>'})
};
fetch('https://win.oneshotagent.com/v1/tools/linkedin/message', 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/message",
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>',
'profile' => '<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/message"
payload := strings.NewReader("{\n \"account_id\": \"<string>\",\n \"profile\": \"<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/message")
.header("Idempotency-Key", "<idempotency-key>")
.header("Content-Type", "application/json")
.body("{\n \"account_id\": \"<string>\",\n \"profile\": \"<string>\",\n \"text\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://win.oneshotagent.com/v1/tools/linkedin/message")
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 \"profile\": \"<string>\",\n \"text\": \"<string>\"\n}"
response = http.request(request)
puts response.read_bodySends one message to a person. If the account already has a 1:1 conversation with them, the message goes into it. If not, OneShot starts one. Use it after an invitation is accepted, or for a connection the account has never messaged. To answer inside a conversation you already have the id of, use Reply.
Paid: flat price per message (see Pricing). Requires the
Whether the person is a connection is checked at send time, not here. See Outcome.
reply grant and an Idempotency-Key header. It shares the reply daily cap and pacing: to LinkedIn, a reply and a message are the same thing.
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, so a retry never sends twice.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 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
The connection. Takes what Invite takes: an HTTPS
linkedin.com/in/ profile URL, a slug or a provider id.string
required
1–4000 characters.
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 | Not yours. |
| 403 | grant_denied / grant_revoked | The human did not grant reply, or revoked. |
| 409 | account_not_ready | Account is reconnect_required / error. |
| 400 | content_blocked | Content safety. |
| 429 | account_send_limit | Daily cap for this account reached, counting replies and messages together (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": "message", "account_id": "…",
"scheduled_for": null,
"headroom": { "limit": 50, "used": 3, "pending": 1, "resets_at": "2026-10-09T00:00:00.000Z" }
}
}
Outcome
PollGET /v1/requests/{request_id} (the SDK waits for you):
-
completed:
{ "action_request_id", "status": "sent", "conversation_id", "message_id", "provider_message_id", "sent_at" }.conversation_idis the 1:1 conversation the message went into, whether it existed or was just created. It then appears in Conversations and Messages, and you can keep the thread going with Reply. -
failed, with an
error_code. Two of them mean “defer”, not “give up”:not_connected: the person is not a first-degree connection of the account. Send an invitation and try again once it is accepted.rate_limited: LinkedIn throttled the account. Retry later with a new key.
target_not_found(no profile matches),grant_revoked,account_disconnected,content_rejected,account_send_limit. The failed-job sweep refunds messages 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 looks for the message in the chat every few minutes, re-attempts once if there is still no trace after 10 minutes, and after 24 hours without confirmation fails the job withsend_unverified.
Example
const sent = await agent.linkedinMessage({
accountId,
profile: 'https://www.linkedin.com/in/jane-doe/',
text: 'Thanks for connecting. Are you free for 15 minutes next week?',
idempotencyKey: 'cadence:jane:first-message',
});
console.log(sent.conversation_id);