Sending

Every send needs an Idempotency-Key and spends a credit.

POST
Send a message

/accounts/{id}/conversations/{conversationId}/messages  ·  needs send

Body: { "text": "…" }. The Idempotency-Key header is required.

curl -X POST https://messengeragent.dev/api/v1/accounts/ACCOUNT_ID/conversations/CONVERSATION_ID/messages \
  -H "Authorization: Bearer $API_KEY" \
  -H "Idempotency-Key: order-1001-shipped" \
  -H "Content-Type: application/json" \
  -d '{"text": "Your order shipped"}'

201 means it was sent:

HTTP/1.1 201 Created

{ "id": "WAMID.…", "conversationId": "15551230001@c.us", "status": "sent" }

202 means it was accepted and will be sent — LinkedIn paces its messages. Read the conversation to follow it: the message appears with the status queued, then sending, then sent — or failed, which is also how running out of credits shows up on LinkedIn.

HTTP/1.1 202 Accepted

{ "id": "q-01a115b1…", "conversationId": "Mi1hYmMvZGVmKz09", "status": "queued" }

Line breaks (LinkedIn)

A LinkedIn message may contain line breaks, in text (JSON "\n", or a plain newline in a multipart field). Each one is typed as a new paragraph in LinkedIn's composer, never as Enter, so a message is never sent early. Before anything is queued the text is normalised:

  • \r\n and \r become \n; three or more line breaks in a row become two; line breaks around the whole message are dropped.
  • So the same message with other line endings is the same message: under one Idempotency-Key it replays the first answer rather than being refused as a different request. The stored text, and what a conversation shows, is the normalised one.
  • At most 20 line breaks (counted after collapsing) — more is 400 too_many_line_breaks — and at most 8000 characters (capabilities.maxTextLength).
  • No other control characters: a tab, NUL or DEL is 400 invalid_request.
  • A message is judged before the Idempotency-Key is used, so a refused one leaves the key free for the corrected message.
  • WhatsApp and Telegram take their text as it always did; this is about LinkedIn.
# Line breaks: write "\n" in JSON. Three paragraphs, sent as three paragraphs.
curl -X POST https://messengeragent.dev/api/v1/accounts/ACCOUNT_ID/conversations/CONVERSATION_ID/messages \
  -H "Authorization: Bearer $API_KEY" \
  -H "Idempotency-Key: intro-ada-1" \
  -H "Content-Type: application/json" \
  -d '{"text": "Hi Ada,\n\nI enjoyed your talk on queues.\n\nCould we speak next week?"}'

POST
Start a conversation

/accounts/{id}/messages  ·  needs send

Message someone you haven't talked to yet. to is a phone number on WhatsApp, a profile username or URL on LinkedIn. The response is the same as a send, and its conversationId is the id to read it by.

# WhatsApp: a phone number        Telegram: a chat id, @username or +number        LinkedIn: a username or profile URL
curl -X POST https://messengeragent.dev/api/v1/accounts/ACCOUNT_ID/messages \
  -H "Authorization: Bearer $API_KEY" \
  -H "Idempotency-Key: welcome-42" \
  -H "Content-Type: application/json" \
  -d '{"to": "+15551230099", "text": "Nice to meet you."}'

POST
Send a file

/accounts/{id}/messages  and  /accounts/{id}/conversations/{conversationId}/messages  ·  needs send

Both send routes also accept multipart/form-data: the same fields as the JSON body (to, subject, text, html) plus one file in a part named file. A JSON request works exactly as before. The answer is the usual one: 202 queued (LinkedIn) with the same { id, conversationId, status }, and the file goes out with the message through the same queue.

# Same fields as the JSON body, plus one file part named "file". The Idempotency-Key header works as it always does.
curl -X POST https://messengeragent.dev/api/v1/accounts/ACCOUNT_ID/conversations/CONVERSATION_ID/messages \
  -H "Authorization: Bearer $API_KEY" \
  -H "Idempotency-Key: cv-ada-1" \
  -F "text=Here is my CV" \
  -F "file=@cv.pdf;type=application/pdf"

# Starting a conversation takes to / subject / text / html and the same file part:
curl -X POST https://messengeragent.dev/api/v1/accounts/ACCOUNT_ID/messages \
  -H "Authorization: Bearer $API_KEY" \
  -H "Idempotency-Key: cv-ada-2" \
  -F "to=ada-lovelace" \
  -F "text=Hello Ada" \
  -F "file=@cv.pdf;type=application/pdf"
  • Which channels. LinkedIn, Telegram (on Telegram the file goes at once, and its text is a caption of up to 1,024 characters; a file alone is a message). WhatsApp answers 422 unsupported_operation when a file is attached. Branch on the account's capabilities.attachments (a boolean) and capabilities.maxAttachmentBytes (an integer; 0 where files are not supported) rather than on the channel's name.
  • Types and size. application/pdf, image/png and image/jpeg, up to 10 MB (10,485,760 bytes). Any other type is 422 attachment_type_not_allowed; a larger file is 413 attachment_too_large. The file's own bytes are checked as well as the declared type — a PDF must begin with %PDF- — and a mismatch is also attachment_type_not_allowed.
  • If the file can't be stored. The answer is 503 attachment_storage_unavailable with a Retry-After — "The file could not be stored. Nothing was queued." — a different code from channel_unavailable (linkedin-api unreachable), so you can tell them apart. The key stays free, so retry with the same Idempotency-Key. While file storage isn't set up, capabilities.attachments is false (and maxAttachmentBytes 0), so you can see it before sending; sending a file anyway gets this same code.
  • Shape. More than one file, a file in a part with another name, a file with no file name, or an empty file is 400 invalid_request. A multipart body with no file is a plain send. On LinkedIn the text may be left out when a file is attached.
  • LinkedIn takes a file only into a conversation that exists. To someone you have never messaged, the first message can carry text and nothing else, so a file sent to them is refused at once with 422 attachment_needs_conversation (not accepted and failed later). Send the text first, then the file into the returned conversation once that message has gone.
  • Retries. With an Idempotency-Key, the same key and the same file (same name, size and content) replays the first result with Idempotent-Replayed: true and sends nothing. The same key with a different file, or with no file, is 422 idempotency_key_reused. Only the file's name, size and a SHA-256 of its content are kept to tell requests apart, never the file.
  • Credits. A message with a file costs the same as one without: one credit, spent when it is actually sent.
  • Privacy. The activity log records that a file was attached, its size and its type. Not its name, not its contents. The file name is treated as personal data and is not logged.

Idempotency

Networks fail halfway. Every send — and every account you create or start signing in — needs an Idempotency-Key header — any unique value for that message, up to 128 characters, such as a UUID or your own order id. If you don't hear back, send the same request with the same key: you get the first result back, marked Idempotent-Replayed: true, and nothing is sent twice.

  • The same key with a different body is refused (422) — a new message needs a new key. For a send with a file, the file counts as part of the body: its name, size and a SHA-256 of its content are compared, never the bytes.
  • Keys are remembered for 24 hours and belong to the API key that used them.
  • A refused send (no credits, account signed out, bad input) frees its key, so you can fix the cause and try again with it.
  • One honest limit: if a channel fails after it may already have sent — a timeout reaching WhatsApp, say — the answer is an error and the key is freed, so a retry could in rare cases send twice. Check the conversation before retrying such a failure.