Inbox

List LinkedIn conversations, read message history, reply with text, attachments or voice notes, and react to replies programmatically.

The inbox is every LinkedIn conversation your connected sender accounts are part of, in one place. Campaign steps, direct actions and manual messages all land in the same threads, so the inbox API is where you build reply handling: routing hot leads to a CRM, powering a custom unibox, or auto-responding to replies.

The chat and message model

A chat is one conversation thread between a sender account and a LinkedIn member. A message is one entry inside a chat.

Key chat fields:

FieldTypeMeaning
idstringThe chat id used in every /inbox/chats/{id} call.
accountIdstring, nullThe sender account on your side of the thread.
attendeeobject, nullSnapshot of the LinkedIn person on the other side.
leadIdstring, nullThe workspace lead this person maps to, when tracked.
lastMessageobject, nullThe most recent message in the thread.
lastMessageAtdate-time, nullWhen the thread last had activity.
lastReplyAtdate-time, nullWhen the lead last messaged this thread. Null until they answer, and never cleared once set: your own replies do not reset it.
unreadCountintegerUnread messages in the thread.
favorite, archivedbooleanInbox flags.

Key message fields:

FieldTypeMeaning
id, chatIdstringIdentity and parent thread.
textstringThe message body.
isFromMebooleantrue for messages your sender account sent.
isEventbooleantrue for system events rather than regular messages.
attachmentsarrayMedia sent with the message (image, video, audio, file), currently populated for outgoing messages. Voice notes carry voiceNote: true and durationMs.
atdate-timeWhen the message was sent.

List and filter chats

listInboxChats returns chats ordered by most recent activity, with standard limit (1 to 100, default 50) and offset paging.

The replied query parameter filters on whether the lead has answered:

  • replied=true: only threads where the lead has sent at least one message. These are ordered by the lead's most recent reply instead of general activity, so the freshest replies come first. Threads stored before reply tracking have no recorded reply time and sort last.
  • replied=false: only threads still awaiting a first reply.
  • Omit it for all chats.
curl "https://app.swarmhit.com/api/v1/inbox/chats?replied=true&limit=25" \
  -H "Authorization: Bearer swh_live_..."

This is the polling-friendly view of "who answered me": page through replied=true and compare each chat's lastReplyAt against your own high-water mark. For push instead of polling, see reacting to replies below.

Read a conversation

Fetch a single thread with getInboxChat, and its history with listChatMessages:

curl "https://app.swarmhit.com/api/v1/inbox/chats/CHAT_ID/messages?limit=50" \
  -H "Authorization: Bearer swh_live_..."

Use isFromMe to render the two sides of the conversation and isEvent to filter out system entries.

If you want a lead-centric view rather than a thread-centric one, getLeadActivity merges campaign step transitions and inbox messages into one newest-first timeline for a lead (capped at 200 events).

Mark a chat read

markChatRead clears the unread count for the chat, and for every sibling thread with the same person, both locally and on LinkedIn:

curl -X POST "https://app.swarmhit.com/api/v1/inbox/chats/CHAT_ID/read" \
  -H "Authorization: Bearer swh_live_..."

Reply in a chat

sendChatMessage sends a message into an existing thread:

curl -X POST "https://app.swarmhit.com/api/v1/inbox/chats/CHAT_ID/messages" \
  -H "Authorization: Bearer swh_live_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 4f2c9a1e-7d31-4b8a-9f0e-2d6c8b5a1e3f" \
  -d '{"text": "Thanks for connecting! Happy to share more."}'

Always send an Idempotency-Key header (any unique string, for example a UUID). Reusing the key replays the first response instead of sending again, so a network retry cannot double-message a lead. A 409 with code: idempotency_in_progress means the original request is still in flight (retry shortly); code: idempotency_failed means the original errored after it may have taken effect (retry with a new key).

The response includes messageId and, when the chat's counterpart is a campaign lead, a campaignLead object with its id and status so your app can offer to pause or stop the sequence.

This endpoint is for existing threads. To start a brand new conversation with a connection, use the direct action sendDirectMessage instead. See Direct actions.

Attachments and voice notes

text becomes optional when you provide an attachment or a voice message. Attachments are fetched server-side from a public url (photo, video or document, max 15 MB each):

curl -X POST "https://app.swarmhit.com/api/v1/inbox/chats/CHAT_ID/messages" \
  -H "Authorization: Bearer swh_live_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 9b7d3e5f-1a24-4c6d-8e90-3f5a7c9b1d2e" \
  -d '{
    "text": "Here is the one-pager I mentioned.",
    "attachments": [
      { "url": "https://example.com/files/one-pager.pdf", "name": "one-pager.pdf" }
    ]
  }'

voiceMessage sends a LinkedIn voice note: the audio at its url is fetched and transcoded to LinkedIn's format server-side (max about 1 minute), with an optional durationMs.

Halting the sequence on send

If the chat's lead is running in a campaign, replying manually via the API does not interrupt the sequence by itself. Use sequenceAction to halt it on send:

  • "pause": freezes the sequence, keeping step progress and timers, so it can be resumed later.
  • "stop": terminal. The executor never contacts this lead again in that campaign.
  • Omit it (or send null) to leave the sequence running.
curl -X POST "https://app.swarmhit.com/api/v1/inbox/chats/CHAT_ID/messages" \
  -H "Authorization: Bearer swh_live_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: c1d2e3f4-5a6b-4c7d-8e9f-0a1b2c3d4e5f" \
  -d '{"text": "Great, let me send over some times.", "sequenceAction": "stop"}'

You can also pause, resume or stop the lead independently of sending, via pauseLeadInCampaign, resumeLeadInCampaign and stopLeadInCampaign. Note that a manual message sent from the inbox inside the Swarmhit app pauses the lead automatically; the API gives you the explicit sequenceAction control instead.

React to replies programmatically

Polling replied=true works, but the push path is webhooks. Subscribe an endpoint to message.received and Swarmhit calls you the moment a lead replies:

curl -X POST "https://app.swarmhit.com/api/v1/webhooks" \
  -H "Authorization: Bearer swh_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/hooks/swarmhit",
    "events": ["message.received"],
    "classifyReplies": true
  }'

The message.received payload carries everything you need to act without extra lookups:

  • data.source: campaign when the reply is on a campaign lead, direct when it is on a lead you actioned via the API.
  • data.accountId: the sender account the reply arrived on.
  • data.campaignId and data.campaignLeadId: set for campaign replies, null for direct ones.
  • data.leadId and data.chatId: the lead and the thread, so you can fetch history and reply.
  • data.message: the reply text and its timestamp.
  • data.from: the replier's firstName, lastName, publicIdentifier and providerId.

A typical handler: fetch the thread with data.chatId via listChatMessages for context, send a reply with sendChatMessage, then markChatRead. The outbound mirror event, message.sent, fires for outgoing messages on tracked-lead chats, so you can keep both sides of the conversation in sync in your own system.

Deliveries are at-least-once: deduplicate on the X-Swarmhit-Delivery header and verify the X-Swarmhit-Signature HMAC. The Webhooks guide covers both.

Reply classification

With classifyReplies enabled on a webhook endpoint, every message.received delivery to it carries a classification.interest verdict: interested, not_interested or neutral, or null when no classification was produced (treat null as unknown, never as neutral). Classification consumes workspace credits. The full contract, vocabulary and example payload live in the Webhooks guide.

To persist the verdict on the campaign membership, call setLeadInterest with interested or not_interested (the stored interest field has no neutral value; its companion interestBy records whether it was set by manual action or ai). Campaigns can also do this themselves when aiInterestEnabled is on.

Reference

On this page