Sender accounts
Connect, monitor, pause and reconnect the LinkedIn accounts that execute your outreach.
A sender account is a connected LinkedIn account that Swarmhit acts from. Campaign steps, direct actions and inbox conversations all execute through a sender. The Accounts endpoints let you connect senders, watch their health, daily limits and usage, pause them, and recover them when LinkedIn interrupts a session.
Credentials are stored encrypted and are never returned by any endpoint. Each connected sender occupies a seat in its workspace: with an organization key and the X-Swarmhit-Workspace header set, the sender lands in that workspace and consumes a seat from its allocation (see Organization).
Connecting a sender
POST /accounts takes the sender's LinkedIn username (email) and password, plus an optional proxy and 2-letter country. There are two modes:
- Automatic (infinite) login: include a
totpSecret(the account's 2FA authenticator setup key). The sign-in completes hands-free, solving a 2FA challenge from the secret, and the sender reconnects on its own later. Any other verification step fails with400and anaccount.connection_failedwebhook rather than leaving a half-connected sender holding a seat. - Interactive: omit
totpSecret. If LinkedIn asks for a verification step, the response is200withstatus: "checkpoint_required"and acheckpointobject describing it.
curl -X POST https://app.swarmhit.com/api/v1/accounts \
-H "Authorization: Bearer swh_live_..." \
-H "Content-Type: application/json" \
-d '{
"username": "jane@example.com",
"password": "her-linkedin-password",
"totpSecret": "JBSWY3DPEHPK3PXP",
"country": "US"
}'Connection failures carry a stable error.code to branch on (invalid-credentials, rate-limited, proxy-failed, provider-timeout, provider-unreachable, checkpoint-restricted, already-connected, no-plan, seat-limit-reached, and more). The full list is on the connect reference; the same code is included on the account.connection_failed webhook.
Sign-in checkpoints
A checkpoint is a verification step LinkedIn requires before it finishes signing the sender in. The checkpoint object tells you what kind and what answers it:
| Type | What LinkedIn wants | input |
|---|---|---|
2FA | A code from the account's authenticator app | code |
OTP | A one-time code sent by email or SMS | code |
IN_APP_VALIDATION | Approval in the LinkedIn mobile app | none |
PHONE_REGISTER | A phone number to register | phone |
CAPTCHA | A puzzle only LinkedIn's own page can show | hosted |
Answer with POST /accounts/{id}/checkpoint: send the verification code, the phone number with its dialling code in brackets (for example (+33)0612345678), or the literal TRY_ANOTHER_WAY to make LinkedIn offer a different method (the way out of an in-app approval nobody can reach). The response is status: "connected" once the sender is live, or checkpoint_required with the next checkpoint when LinkedIn chains another step.
Checkpoints expire five minutes after LinkedIn asks (expiresAt on the checkpoint object). Past that window the attempt is dropped and answering returns 400 with code: checkpoint_expired; reconnect the sender to start a fresh attempt.
IN_APP_VALIDATION and CAPTCHA are completed outside the API, so poll GET /accounts/{id}/checkpoint (or listen for account.status_changed): it re-reads the live session and returns resolved: true as soon as the sender is up. POST /accounts/{id}/checkpoint/resend asks LinkedIn for a fresh code or a new approval push when the pending type supports it (canResend on the checkpoint).
Status and health
GET /accounts and GET /accounts/{id} return each sender with its status, a more granular connectionStatus string, limits, usage, pause state, InMail credit balance, sign-in country and cached Social Selling Index.
status | Meaning | What to do |
|---|---|---|
active | The sender is signed in and available for outreach. | Nothing. |
pending | The sign-in has not completed yet (for example, a checkpoint is waiting). | Answer or poll the checkpoint. |
error | The LinkedIn session broke. | Reconnect the sender. |
suspended | LinkedIn has restricted the account. | Clear the restriction on linkedin.com, then connect it again. |
The account.status_changed webhook reports transitions between these states and can also report disconnected when the session is gone.
When a sender needs reconnecting
Send endpoints return 409 when the sending account was rejected by LinkedIn and may need reconnecting. When that happens, retry the saved sign-in with POST /accounts/{id}/reconnect. It applies to credentials and infinite-login senders (400 for cookie or hosted accounts) and runs one attempt now. If the person changed their LinkedIn password or 2FA, pass the new password (and totpSecret) to replace the stored credentials first:
curl -X POST https://app.swarmhit.com/api/v1/accounts/aCc123/reconnect \
-H "Authorization: Bearer swh_live_..." \
-H "Content-Type: application/json" \
-d '{ "password": "the-new-password" }'The response status is reconnected, checkpoint_required (answer the returned checkpoint as above; a stored authenticator secret clears a 2FA challenge on its own) or failed.
Reconnect also recovers a sender whose session no longer exists at all — for example one that was parked while the workspace had no active plan. As long as the stored credentials are intact it simply signs in fresh; this needs an available sender seat on the plan, and returns 400 when there is none.
If the account is up but its stored identity (name, headline, picture, paid-plan flag) looks stale, POST /accounts/{id}/sync refreshes it from LinkedIn.
Limits and usage
Each account carries two objects on the API:
limits: the sender's per-day sending caps.usage: today's action tally against those caps.
When an action would exceed a cap, the endpoint returns 429 with details: { limit, used, scope, resetAt }, where scope is day (the daily cap), week (the weekly connection-request limit: about 130 invites per week for free sender accounts, 150 for paid) or pending (the pending-invitation backlog, which clears as invitations are accepted or withdrawn). resetAt is the UTC time the window frees; it is absent for pending.
When invite limits park sends, the account's cooldownUntil is stamped with the time the cooldown ends (cooldownReason says why); it is null when no cooldown is active.
Sender tier also shapes what an action can include: connection-request notes are a premium feature capped at 300 characters, and free senders' invites go out without the note. See LinkedIn safety for how to stay within healthy volumes.
Pausing and resuming
POST /accounts/{id}/pause stops all activity from a sender: campaigns stop acting from it, and direct actions are rejected with 409 code: sender_paused until it is resumed with POST /accounts/{id}/resume. Both are no-ops when the sender is already in the target state, and both still return the account.
curl -X POST https://app.swarmhit.com/api/v1/accounts/aCc123/pause \
-H "Authorization: Bearer swh_live_..."On the account, paused is true, pausedAt is when it happened, and pausedReason says why:
user: paused manually (or via this endpoint).inmail_credits: auto-paused because its InMails could not be sent. Swarmhit resumes it automatically once InMail credits are available again (theaccount.resumedwebhook fires withby: system).
A pause does not change the sender's connection status, and its campaign leads stay assigned to it. To keep a campaign moving while a sender is down, move its not-yet-contacted leads to the rest of the pool (see rebalancing below).
InMail credits
inmailCredits on the account is the InMail credit balance of the sender's LinkedIn plan: balance (or null when unknown) and syncedAt. It is null when it has never been synced. Sending an InMail from a sender without premium InMail access fails with 409 code: inmail_not_available; a sender that is out of credits fails with 429 code: inmail_credits_exhausted (details.scope is inmail_credits).
Social Selling Index
LinkedIn scores every member 0 to 100 on how well their profile sells socially. The score is made of four pillars worth up to 25 each, and LinkedIn also reports where the profile sits against its industry and its own network.
GET /accounts/{id}/ssi returns it, and the same object rides along as ssi on the account itself:
curl https://app.swarmhit.com/api/v1/accounts/aCc123/ssi \
-H "Authorization: Bearer swh_live_..."{
"data": {
"score": 62.4,
"brand": 17.1,
"people": 14.8,
"insights": 12.2,
"relationships": 18.3,
"industryTop": 8,
"networkTop": 3,
"industryName": "Computer Software",
"unavailable": false,
"syncedAt": "2026-08-04T09:12:44.120Z"
}
}| Field | Pillar |
|---|---|
brand | Establish your professional brand |
people | Find the right people |
insights | Engage with insights |
relationships | Build relationships |
industryTop and networkTop are percentiles: 8 means the profile is in the top 8% of its industry.
The score is cached on the account and refetched once it goes stale, since LinkedIn recomputes it daily at most. Pass refresh=true to force a live read. It is also refreshed as part of POST /accounts/{id}/sync.
unavailable: true means LinkedIn is not exposing a score for that sender's session, and every number is null. That is a state, not an error: the endpoint still returns 200. Treat it the same way as an unsynced InMail balance rather than retrying.
Sender performance
GET /accounts/{id}/stats reports how a sender has actually performed, over a window of 7 to 90 days (days, default 30). Campaign sends and direct actions are both counted, so an API-driven workspace sees its full volume.
curl "https://app.swarmhit.com/api/v1/accounts/aCc123/stats?days=30" \
-H "Authorization: Bearer swh_live_..."The response carries:
cards: connection requests, acceptances, messages, replies and interested leads for the window, each withdeltaPctagainst the preceding window of the same length.series: one entry per calendar day, for charting.lifetimeandrates: all-time totals, acceptance rate and reply rate.byCampaignandpoolCampaigns: the per-campaign split, plus campaigns the sender is pooled into but has not sent from yet.actions: direct (non-campaign) actions in the window, by type.limits: every cap in force with how much is already used, both the daily caps and the rolling weekly ones.
limits is the endpoint to poll before a large batch: it tells you the remaining headroom on each action without having to hit a 429 first.
Daily series buckets are calendar days on the server's clock, while the daily caps in limits.daily reset at 00:00 UTC. The two can disagree by a few hours near midnight.
Senders and campaigns
A campaign's linkedinAccountIds is its sender pool: the LinkedIn sender accounts eligible to run its leads. Each lead is stickily assigned to one of these on its first action tick (the executor picks the sender with the most remaining daily quota for the upcoming step), and once the lead is contacted the sender is pinned: every subsequent step runs from it. LeadInCampaign.linkedinAccountId exposes the assignment (null until a sender is assigned). Add multiple ids to scale outreach beyond a single account's daily cap.
Editing the pool with PATCH /campaigns/{id}:
- An empty
linkedinAccountIdsis rejected with400while the campaign is (or is being set)active; pause it first. On a paused campaign it clears the pool and leads wait until a sender is added. - Removing a sender reassigns its in-flight leads to the remaining pool on their next run.
Rebalancing a paused or failed sender's leads
POST /campaigns/{id}/senders/{accountId}/rebalance moves a paused or failed sender's not-yet-contacted leads in that campaign onto the campaign's other available senders, evenly. Leads the sender already contacted stay pinned to it.
curl -X POST https://app.swarmhit.com/api/v1/campaigns/cMp456/senders/aCc123/rebalance \
-H "Authorization: Bearer swh_live_..."The response reports how many leads moved and how many receivers took them. receivers: 0 means no other sender was available and nothing moved (still 200); 422 means the account is not in that campaign's sender pool.
Disconnecting
DELETE /accounts/{id} revokes the sender's session, removes it from the workspace and frees its seat. It is permanent: reconnecting later means connecting the sender again from scratch. Note that deleting a workspace from an organization is rejected with 400 while it still has connected senders; disconnect them first.
Reacting to account events
Account lifecycle changes are pushed as webhooks: account.connected, account.connection_failed, account.status_changed, account.checkpoint_required, account.paused and account.resumed. Subscribing to account.status_changed and account.checkpoint_required is the reliable way to catch a sender going down and bring it back without polling.
Reference
List sender accounts
Health, daily limits and usage, pause state, cooldowns and InMail credits for every sender.
Connect a sender
Sign a LinkedIn account in, optionally hands-free with a 2FA setup key.
Reconnect a sender
Retry the saved sign-in after a session breaks, with optional new credentials.
Social Selling Index
LinkedIn's 0-100 score for a sender, its four pillars and its industry and network percentiles.
Sender performance
Sends, acceptances, replies, the per-campaign split and remaining limit headroom over a date range.
Rebalance a sender's leads
Move a paused or failed sender's uncontacted campaign leads onto the rest of the pool.