Skip to content
Docs

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#

Shell
curl -sS "https://direct.lempile.com/api/v1/chats?account_id=$ACCOUNT_ID&limit=20" \
  -H "X-API-KEY: $DIRECT_API_KEY"
JSON
{
  "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, unread or a limit over 20 walk the inbox and the archive by last activity, 25 a call, so a cursor reaches the whole inbox.
  • type is 0 for one person, 1 for a group. attendee_provider_id is the other person in a one-to-one chat.
  • read_only: 1 when 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.
  • folder holds the chat's box (INBOX, ARCHIVED, OTHER, PENDING, SPAM) and INBOX_LINKEDIN_CLASSIC.
  • pinned is always 0; muted_until is -1 when muted.

One Chat#

Shell
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#

Shell
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"
JSON
{
  "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). limit 1 to 250 (default 20).
  • before, after (ISO 8601 UTC) and sender_id (a provider id) filter, as on Unipile.
  • is_sender is 1 for the account's own messages. deleted: 1 (with deleted_for: "everyone") when its sender took it back, edited: 1 when edited, is_event: 1 for an event LinkedIn writes in the conversation.
  • reactions holds the account's own reactions only; other people's reactions to a message aren't read. seen is always 0 and seen_by always {}.
  • attachments: images (type: "img", with size), files (type: "file", with file_name, file_size, mimetype) and voice notes (type: "audio", voice_note: true, duration in milliseconds, mimetype: "audio/mp4"), each with a url (LinkedIn's signed link, which expires). An image LinkedIn hasn't processed yet comes with unavailable: true and no url.
  • subject on an InMail.

GET /messages/{message_id}?account_id= answers one Message (with account_type: "LINKEDIN"), or 404 when LinkedIn doesn't know it.

Attendees#

Shell
curl -sS "https://direct.lempile.com/api/v1/chats/$CHAT_ID/attendees?account_id=$ACCOUNT_ID" \
  -H "X-API-KEY: $DIRECT_API_KEY"
JSON
{
  "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: 1 and network_distance: "SELF".
  • GET /chat_attendees?account_id= lists everyone the account has a conversation with, newest conversation first, with a cursor (limit 1 to 250, default 100). With chat_id, that chat's attendees.
  • GET /users?account_id= without provider_url answers the same list.

Start A Chat#

Shell
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."
  }'
JSON
{
  "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_ids holds one person: a provider id, a public identifier or a profile URL. Several (a group conversation) answer 501.
  • linkedin[api] is classic or absent; recruiter and sales_navigator answer 501, as do linkedin[topic], linkedin[applicant_id], linkedin[invitation_id] and linkedin[group_id].
  • text is 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#

Shell
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." }'
JSON
{ "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:

Shell
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[] or attachments[0], attachments[1]…, and video_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_large above). 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) answers 501.

Voice Notes#

Shell
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"
JSON
{ "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 text or files it answers 501.
  • 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 answers 415 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 its audio (its asset, size and length). ?dry_run=true shows all three, with the note's length and what it came as, and sends nothing.
  • Read back, it's an attachment of type: "audio" with voice_note: true and its duration in milliseconds.

Delete A Message#

Shell
curl -sS -X DELETE "https://direct.lempile.com/api/v1/messages/$MESSAGE_ID?account_id=$ACCOUNT_ID" \
  -H "X-API-KEY: $DIRECT_API_KEY"
JSON
{ "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_entity after). One already deleted answers 422 errors/action_already_performed.
  • account_id is required, as on every inbox call.

React To A Message#

Shell
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": "👍" }'
JSON
{ "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#

Shell
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" }'
JSON
{ "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#

Shell
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 }'
JSON
{ "object": "ChatPatched" }
  • action: setReadStatus (value: false marks it unread) or setMuteStatus. 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=true and an Idempotency-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?