Chats And Messages
The LinkedIn inbox: chat and message ids, chats, messages, attendees, starting a chat, sending messages and files.
The account's LinkedIn inbox, in Unipile's Chat, Message and ChatAttendee objects. Every call names its account with account_id, the writes included.
Chat And Message IDs#
- A chat's id is LinkedIn's own conversation id (
2-and base64), a message's LinkedIn's own message id. - Direct answers them URL-safe and unpadded (
+becomes-,/becomes_, no trailing=), so they fit[A-Za-z0-9_-]and go in a path as they are:2-QUFBQXRocmVhZPv_b25lMQ. - Every route that takes one also takes LinkedIn's own form, URL-encoded (
2-QUFBQXRocmVhZPv%2Fb25lMQ%3D%3D), and maps it back. - They are not Unipile ids: an id stored from Unipile doesn't resolve here. Read the chats again from Direct.
List Chats#
curl -sS "https://direct.lempile.com/api/v1/chats?account_id=$ACCOUNT_ID&limit=20" \
-H "X-API-KEY: $DIRECT_API_KEY"{
"object": "ChatList",
"items": [
{
"object": "Chat",
"id": "2-QUFBQXRocmVhZPv_b25lMQ",
"account_id": "00000000-0000-4000-8000-0000000000a1",
"account_type": "LINKEDIN",
"provider_id": "2-QUFBQXRocmVhZPv_b25lMQ",
"name": null,
"type": 0,
"timestamp": "2026-10-29T09:41:07.000Z",
"unread_count": 1,
"archived": 0,
"muted_until": null,
"read_only": 0,
"folder": ["INBOX", "INBOX_LINKEDIN_CLASSIC"],
"pinned": 0,
"attendee_provider_id": "ACoAAAzyxwvutsrqponml"
}
],
"cursor": "<cursor>"
}| Parameter | |
|---|---|
limit |
1 to 250 (default 10). |
cursor |
The previous page's cursor. |
unread |
true: only chats with unread messages. |
before, after |
Only chats last active before or after this time (ISO 8601 UTC, e.g. 2026-10-01T00:00:00.000Z). |
account_type |
LINKEDIN or nothing; anything else answers 400. |
- Newest activity first, the inbox and the archive together (LinkedIn's spam folder isn't read).
- The newest 20 come from LinkedIn's inbox sync in one call, kept for a minute. Further pages,
before,after,unreador alimitover 20 walk the inbox and the archive by last activity, 25 a call, so a cursor reaches the whole inbox. typeis0for one person,1for a group.attendee_provider_idis the other person in a one-to-one chat.read_only: 1when nobody can answer (LinkedIn's sponsored messages:content_type: "linkedin_offer"for the ones LinkedIn labels "LinkedIn Offer","sponsored"for the others).content_type: "inmail"for an InMail.folderholds the chat's box (INBOX,ARCHIVED,OTHER,PENDING,SPAM) andINBOX_LINKEDIN_CLASSIC.pinnedis always0;muted_untilis-1when muted.
One Chat#
curl -sS "https://direct.lempile.com/api/v1/chats/$CHAT_ID?account_id=$ACCOUNT_ID" \
-H "X-API-KEY: $DIRECT_API_KEY"The Chat, with its latest message as lastMessage. Found among the newest conversations, else read from LinkedIn by its id; an unknown id answers 404.
Messages#
curl -sS "https://direct.lempile.com/api/v1/chats/$CHAT_ID/messages?account_id=$ACCOUNT_ID&limit=20" \
-H "X-API-KEY: $DIRECT_API_KEY"{
"object": "MessageList",
"items": [
{
"object": "Message",
"id": "2-_vptc2cwMTI5IQ",
"provider_id": "2-_vptc2cwMTI5IQ",
"account_id": "00000000-0000-4000-8000-0000000000a1",
"chat_id": "2-QUFBQXRocmVhZPv_b25lMQ",
"chat_provider_id": "2-QUFBQXRocmVhZPv_b25lMQ",
"sender_id": "ACoAAAzyxwvutsrqponml",
"sender_attendee_id": "ACoAAAzyxwvutsrqponml",
"sender_urn": "urn:li:fsd_profile:ACoAAAzyxwvutsrqponml",
"text": "Sounds good",
"attachments": [],
"timestamp": "2026-10-29T09:41:07.000Z",
"is_sender": 0,
"reactions": [],
"seen": 0,
"seen_by": {},
"hidden": 0,
"deleted": 0,
"edited": 0,
"is_event": 0,
"delivered": 1,
"behavior": null,
"original": "",
"message_type": "MESSAGE",
"attendee_type": "MEMBER",
"attendee_distance": 2
}
],
"cursor": "<cursor>"
}- Newest first, back to the first message (LinkedIn gives 100 a call).
limit1 to 250 (default 20). before,after(ISO 8601 UTC) andsender_id(a provider id) filter, as on Unipile.is_senderis1for the account's own messages.deleted: 1(withdeleted_for: "everyone") when its sender took it back,edited: 1when edited,is_event: 1for an event LinkedIn writes in the conversation.reactionsholds the account's own reactions only; other people's reactions to a message aren't read.seenis always0andseen_byalways{}.attachments: images (type: "img", withsize), files (type: "file", withfile_name,file_size,mimetype) and voice notes (type: "audio",voice_note: true,durationin milliseconds,mimetype: "audio/mp4"), each with aurl(LinkedIn's signed link, which expires). An image LinkedIn hasn't processed yet comes withunavailable: trueand nourl.subjecton an InMail.
GET /messages/{message_id}?account_id= answers one Message (with account_type: "LINKEDIN"), or 404 when LinkedIn doesn't know it.
Attendees#
curl -sS "https://direct.lempile.com/api/v1/chats/$CHAT_ID/attendees?account_id=$ACCOUNT_ID" \
-H "X-API-KEY: $DIRECT_API_KEY"{
"object": "ChatAttendeeList",
"items": [
{
"object": "ChatAttendee",
"id": "ACoAAAzyxwvutsrqponml",
"account_id": "00000000-0000-4000-8000-0000000000a1",
"provider_id": "ACoAAAzyxwvutsrqponml",
"name": "Bob Buyer",
"is_self": 0,
"hidden": 0,
"specifics": {
"provider": "LINKEDIN",
"member_urn": "urn:li:member:456",
"occupation": "Head of Sales",
"headline": "Head of Sales",
"network_distance": "DISTANCE_2"
},
"picture_url": "https://media.licdn.com/dms/image/x/400.jpg",
"profile_url": "https://www.linkedin.com/in/ACoAAAzyxwvutsrqponml"
}
],
"cursor": null
}- The account itself is in the list with
is_self: 1andnetwork_distance: "SELF". GET /chat_attendees?account_id=lists everyone the account has a conversation with, newest conversation first, with a cursor (limit1 to 250, default 100). Withchat_id, that chat's attendees.GET /users?account_id=withoutprovider_urlanswers the same list.
Start A Chat#
curl -sS -X POST "https://direct.lempile.com/api/v1/chats" \
-H "X-API-KEY: $DIRECT_API_KEY" \
-H "Idempotency-Key: first-message-bob" \
-H "Content-Type: application/json" \
-d '{
"account_id": "'"$ACCOUNT_ID"'",
"attendees_ids": ["ACoAAAzyxwvutsrqponml"],
"text": "Hi Bob, quick question about your post."
}'{
"object": "ChatStarted",
"chat_id": "2-QUFBQXRocmVhZPv_b25lMQ",
"message_id": "2-_vptc2cwMTMxIQ",
"id": "2-QUFBQXRocmVhZPv_b25lMQ"
}Status 201. Direct first reads the profile's "Message" option, as LinkedIn's profile page does (one call):
| Case | Answer |
|---|---|
| The account can message the person for free (a connection, an open profile, a reply) | the message is sent |
It can't, and linkedin[inmail] isn't true |
422 errors/no_connection_with_recipient |
linkedin[inmail]=true, and LinkedIn offers no InMail to this person |
422 errors/not_allowed_inmail |
linkedin[inmail]=true, and the account has no InMail credit for them |
422 errors/insufficient_credits |
linkedin[inmail]=true, and an InMail is offered |
an InMail is sent, with subject |
| The account itself | 422 errors/invalid_recipient |
attendees_idsholds one person: a provider id, a public identifier or a profile URL. Several (a group conversation) answer501.linkedin[api]isclassicor absent;recruiterandsales_navigatoranswer501, as dolinkedin[topic],linkedin[applicant_id],linkedin[invitation_id]andlinkedin[group_id].textis 8,000 characters at most, and required unless a file or a voice note is sent.- A voice note (
voice_message, see Voice Notes) starts a chat the same way; never as an InMail (501). - InMail is built from LinkedIn's own web code and covered by tests, but hasn't been confirmed with a real InMail send yet.
Send A Message#
curl -sS -X POST "https://direct.lempile.com/api/v1/chats/$CHAT_ID/messages" \
-H "X-API-KEY: $DIRECT_API_KEY" \
-H "Idempotency-Key: reply-42" \
-H "Content-Type: application/json" \
-d '{ "account_id": "'"$ACCOUNT_ID"'", "text": "Thanks, Tuesday works for me." }'{ "object": "MessageSent", "message_id": "2-_vptc2cwMTMwIQ", "id": "2-_vptc2cwMTMwIQ" }Status 201. id and message_id are the same id, in the form GET /chats/{chat_id}/messages answers it.
Files#
Both writes take files in Unipile's multipart form:
curl -sS -X POST "https://direct.lempile.com/api/v1/chats/$CHAT_ID/messages" \
-H "X-API-KEY: $DIRECT_API_KEY" \
-F "account_id=$ACCOUNT_ID" \
-F "text=The deck we talked about" \
-F "attachments=@deck.pdf;type=application/pdf"- File fields:
attachments,attachments[]orattachments[0],attachments[1]…, andvideo_message(sent as a video). - Other fields in Unipile's bracket form:
attendees_ids[0],linkedin[api],linkedin[inmail]. - Each file is 20 MB at most (
400 errors/content_too_largeabove). Videos go as videos; images and every other file go as files, as LinkedIn's web app sends them. - Files go through LinkedIn's own uploader first (two or three calls each), then the message.
quote_id(quoting a message) answers501.
Voice Notes#
curl -sS -X POST "https://direct.lempile.com/api/v1/chats/$CHAT_ID/messages" \
-H "X-API-KEY: $DIRECT_API_KEY" \
-H "Idempotency-Key: voice-42" \
-F "account_id=$ACCOUNT_ID" \
-F "linkedin[api]=classic" \
-F "voice_message=@voice_message.m4a;type=audio/m4a"{ "object": "MessageSent", "message_id": "2-_vptc2cwMTMyIQ", "id": "2-_vptc2cwMTMyIQ" }Status 201, as for any message; POST /chats takes voice_message too.
- A voice note goes alone, as in LinkedIn's apps: with
textor files it answers501. - Any audio works: m4a (AAC, as Looply records it), mp3, ogg, webm/opus (a browser's recorder), wav, aac or flac, 20 MB at most. Direct turns it into LinkedIn's format, AAC in an .m4a file (AAC audio is kept as it is), and measures it.
- 60 seconds at most, LinkedIn's limit: longer answers
422 errors/invalid_message. A file that isn't audio answers415 errors/unsupported_media_type. Nothing is sent in either case. - LinkedIn gets three calls: the upload's registration (
mediaUploadType: VOICE_MESSAGE), the file, then the message with the voice note as itsaudio(its asset, size and length).?dry_run=trueshows all three, with the note's length and what it came as, and sends nothing. - Read back, it's an attachment of
type: "audio"withvoice_note: trueand itsdurationin milliseconds.
Delete A Message#
curl -sS -X DELETE "https://direct.lempile.com/api/v1/messages/$MESSAGE_ID?account_id=$ACCOUNT_ID" \
-H "X-API-KEY: $DIRECT_API_KEY"{ "object": "MessageDeleted" }Status 200. As LinkedIn's "Delete": the message is taken back for everyone, and reads show it with deleted: 1.
- Only the account's own messages (someone else's answers
403), and only within 60 minutes of sending it, LinkedIn's limit (422 errors/unprocessable_entityafter). One already deleted answers422 errors/action_already_performed. account_idis required, as on every inbox call.
React To A Message#
curl -sS -X POST "https://direct.lempile.com/api/v1/messages/2-QUFBQXRocmVhZPv_b25lMQ/reaction" \
-H "X-API-KEY: $DIRECT_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "account_id": "'"$ACCOUNT_ID"'", "reaction": "👍" }'{ "object": "MessageReactionAdded", "success": true }DELETE /messages/{message_id}/reaction with the same body (or ?reaction=) takes it off: {"object": "MessageReactionRemoved", "success": true}. reaction is a native emoji. The message is read first (one call): an unknown one answers 404.
Edit A Message#
curl -sS -X PATCH "https://direct.lempile.com/api/v1/messages/2-QUFBQXRocmVhZPv_b25lMQ" \
-H "X-API-KEY: $DIRECT_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "account_id": "'"$ACCOUNT_ID"'", "text": "Hi Bob, see you at 3pm" }'{ "object": "MessagePatched" }Only the account's own messages, within 60 minutes of sending them, as LinkedIn allows (as a deletion): Direct reads the message first, and someone else's answers 403 errors/insufficient_permissions, an older one 422 errors/unprocessable_entity.
Mark A Chat Read, Mute It, Delete It#
curl -sS -X PATCH "https://direct.lempile.com/api/v1/chats/2-QUFBQXRocmVhZPv_b25lMQ" \
-H "X-API-KEY: $DIRECT_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "account_id": "'"$ACCOUNT_ID"'", "action": "setReadStatus", "value": true }'{ "object": "ChatPatched" }action:setReadStatus(value: falsemarks it unread) orsetMuteStatus. Unipile's other actions are WhatsApp's and Telegram's:422 errors/not_supported_by_provider.DELETE /chats/{chat_id}?account_id=deletes the conversation from the account's inbox ({"object": "ChatDeleted"}); the other people keep theirs.- The chat is read first (an unknown one answers
404). Every write here takes?dry_run=trueand anIdempotency-Key.
Not Done#
These Unipile messaging routes answer 501 errors/feature_not_implemented: archiving or pinning a chat (WhatsApp's), a chat's sync, a message's attachment download, forwarding, GET /messages and the /chat_attendees/{attendee_id} routes. See Unipile Compatibility.
Was this page helpful?