- Analytics: the overview, performance per identity and the best time to publish.
- Audience: the people who reacted or commented (engagers) and their export to a contacts list.
- Engagement: track any LinkedIn post, comment on it, get AI comment suggestions and add reactions.
- Publishing: schedule a post, list scheduled posts and cancel one.
Social Selling must be enabled for your workspace. Every call answers 403 otherwise: ask your workspace
admin. Read operations need the
SOCIAL_SELLING_READ scope. Write operations need SOCIAL_SELLING_WRITE,
which also grants SOCIAL_SELLING_READ. The endpoint reference lists the scope and rate limit of each
operation.Before you start
Two post ids
A post can carry two ids. Each operation says which one it takes.
A tracked post’s
publishingPostId stays null until you first react to it or comment on it; Enginy creates it then. Comments and comment suggestions accept either id, exactly one per call. Post engagers, reactions and the export postId filter take the tracked postId. Scheduled posts use publishingPostId.
Identity scope
Over MCP, calls run as the connected user. A user restricted to some identities only reaches those identities, as in the app: their posts, analytics and engagers, and only those identities can comment or publish. Their comments, and comment suggestions by post id, only reach posts of those identities. An engager’s lifetime score and total interactions still count every identity of the workspace, as on the Prospects page of the app. The first and last detection times of an engager also cover the posts of every identity (see Posts and engagers). A workspace API key (x-api-key) reaches every identity of the workspace. An identityIds parameter narrows a call inside the caller’s scope and never widens it. The analytics and engager lists ignore ids outside the scope, and the export filter matches nothing for them.
A post, identity or list of another workspace, or one outside the caller’s scope, answers 404. Reactions are the exception: an identityIds entry outside the scope answers 403.
Errors
Analytics
All analytics are read-only, use UTC days and cover the identities the caller may see. LinkedIn metrics are snapshotted daily at 03:00 UTC, so only completed days count.GET /v1/social-selling/analytics/overview: posts, impressions, reactions, comments, people reached and new people reached over a range (last 30 days by default, at most 366). It compares them with the previous period of the same length (previousPeriod,changePct) and adds the impressions, reactions, comments and new people reached of each identity (series; posts and people reached per identity come from/analytics/identities). RequestedidentityIdsthe caller cannot see or that are not identities of the workspace get noseriesrow. AddincludeDaily=truefor one row per day.GET /v1/social-selling/analytics/identities: the same numbers per identity, ranked bysort. It ranks byreachedby default, or byimpressions,reactions,commentsorposts. Each row has anappUrlto the identity’s page.GET /v1/social-selling/analytics/publishing-times: the best weekday and hour to publish, learned from your own posts of the last 90 days. PasstimeZone(IANA,UTCby default) and ametric:engagement(reactions + comments, the default),reactions,commentsorimpressions. It returns the toplimitslots asbestSlots, each with at leastminPostsmeasured posts, and the averages per weekday (byWeekday) and per hour (byHour).
Audience
Posts and engagers
GET /v1/social-selling/posts lists tracked posts newest first. Each post has its latest LinkedIn metrics (impressions, reactions and reactionsByType, comments, reposts), its engaged accounts and new engagers, and its topics and target roles. Filter with identityIds, dateFrom / dateTo (publication day) and includeReposts (off by default). GET /v1/social-selling/posts/{postId} returns one post.
GET /v1/social-selling/engagers lists the people who reacted to or commented on the posts published in a range (last 90 days by default), the Prospects page of the app. Each row has a prospectId, the matching contactId / companyId when they are already in Enginy, a contactStatus (NEW, IN_CRM, CONTACTED), their interactions in the range and their lifetime engagement score. Filter by contactStatus, interaction, company or minEngagementScore, and sort server-side by engagementScore (the default), interactions, reactions or comments.
The rows and the in-range counts (reactionsInRange, commentsInRange, interactionsInRange, identitiesInRange) only cover the identities the caller may see. The lifetime fields cover the whole workspace:
engagementScoreandtotalInteractionscount every identity of the workspace, including identities the caller cannot see, as on the Prospects page of the app. TheminEngagementScorefilter and theengagementScoresort use that same score.- The detection times
firstInteractionAtandlastInteractionAtare when an Enginy sync first and last detected the person on any tracked post of the workspace. They are not engagement times: every sync that sees the person again refresheslastInteractionAt.
GET /v1/social-selling/posts/{postId}/engagers lists the people who engaged with one tracked post: how they reacted, what they commented and whether they are contacts, with LinkedIn’s totals for the post.
Blocklisted people and your own identities never appear as engagers.
Export engagers to a list
Select the engagers in one of three ways:prospectIds (from the engager lists, at most 1,000), a tracked postId, or a dateFrom / dateTo publication range optionally narrowed by identityIds. Then keep interaction (all, commented, reacted) and contacts (new keeps the people not in Enginy yet).
POST /v1/social-selling/engagers/export-preview(read scope) creates nothing. It returnsprospectCount, the LinkedIn enrichment price (creditsPerContact), the credit estimate whenscrapeFromLinkedInis true, youravailableCreditsandwouldExceedBalance.POST /v1/social-selling/engagers/exportadds the same selection to a new list (target.listName) or an existing one (target.listId). One export takes at most 5,000 engagers.
scrapeFromLinkedIn is true: each exported contact is then enriched from LinkedIn at creditsPerContact, and the estimate is an upper bound. Pass maxCredits to refuse the export (422 MAX_CREDITS_EXCEEDED, nothing created) when the estimate is above it.
An export whose selection matches nobody answers 400 with details.code NO_MATCHING_ENGAGERS, and a target.listId that is not a list of your workspace answers 404 with LIST_NOT_FOUND. Nothing is created in either case.
listId and workflowId as soon as it starts, and the contacts appear in the list when the workflow finishes. Existing contacts are reused.
To make a retry safe, send a unique idempotencyKey in the body, or an Idempotency-Key header (the body wins when both are sent). Sent again with the same request, the key returns the first export with replayed: true, starts no new export and charges nothing. With a different request, the key answers 409 with details.code IDEMPOTENCY_KEY_REUSED. A key is bound only once its export starts, so a request refused with 400, 404 or 422 does not use it up. It is then remembered while the export runs and for at least 24 hours after it finishes. It always answers with that first export whatever its outcome, so use a new key to try again. The request is compared by meaning: id order, duplicates, times within a day and explicit defaults do not count, nor do dates when a postId selects the engagers.
Without a key, every call starts a new export: a retry with target.listName creates another list, and with scrapeFromLinkedIn every exported contact is scraped and charged again. A lost response then cannot be told apart from a failed start, because a list of that name can exist either way. Send a key whenever a retry is possible. Once an export finishes, POST /v1/social-selling/engagers/export-preview with the same selection and contacts set to new stops counting the engagers it exported. Engagers without a LinkedIn profile URL are never exported and keep counting.
Engagement
Track a post
POST /v1/social-selling/posts with { "url": "…" } makes any LinkedIn post usable by the comment and comment suggestion operations. It accepts linkedin.com/posts/…, lnkd.in/… and linkedin.com/feed/update/urn:li:activity:… links (also share and ugcPost URNs). Without name, Enginy names the post after the first line of its text, cut to 40 characters.
The response (201) holds the post’s publishingPostId, url, name, content, postedAt and appUrl. trackedPostId and identityId are set when Enginy matches the post to one of your workspace’s identities; they are null for anyone else’s post. The text comes from Enginy’s synced copy of the post, or from the public LinkedIn page when there is none, so content can be empty when LinkedIn refuses the request. Any URL of a post Enginy already has returns that same post. A user restricted to some identities only gets posts of those identities or third-party posts: a post of another identity of your workspace answers 404. They can only comment on posts of their identities, so a third-party post they track answers 404 to the comment and comment suggestion operations; pass its text as postContent to get suggestions.
Comment
POST /v1/social-selling/comments comments from one of your identities on a post already published on LinkedIn. Name the post with postId or publishingPostId, and pass identityId and text (up to 3,000 characters). The response status says what happened:
POSTED: the comment was sent and appears on LinkedIn within minutes.PENDING_APPROVAL: a colleague owns the identity and reviews comments made from it. Enginy emails them a review link, and nothing reaches LinkedIn until they approve it.
reactionsAmount (0 to 100) adds reactions from other identities to a comment that posts right away, and the response’s reactionsScheduled says how many. A PENDING_APPROVAL comment gets no reactions, even after the owner approves it. A user restricted to some identities can only comment on posts of those identities. A retry posts the comment again.
Comment suggestions
POST /v1/social-selling/comment-suggestions writes AI comments for a post, each from a different identity in that identity’s voice and in the post’s language. Give one post source: postId, publishingPostId or the text itself as postContent. Choose the writers with identityIds, or let the AI pick the most relevant identities you may use (never the post’s author). Tune tone, length, useEmojis and count (1 to 6).
Generation takes up to 60 seconds and stores nothing. To publish a suggestion, send its commentText as text with its identityId to the comment operation.
Reactions
POST /v1/social-selling/posts/{postId}/reactions schedules reactions on a tracked post:
amount: draw that many identities from the workspace pool allowed to react.identityIds: or react from exactly these identities.reactionTypes: an optional mix ofLIKE,PRAISE,EMPATHY,INTEREST,APPRECIATIONandENTERTAINMENT.skipAuthor: on by default, so the author never reacts to their own post.
publishingPostId and an appUrl to follow progress.
Publishing
Schedule a post
POST /v1/social-selling/scheduled-posts schedules a text post from one of your identities:
identityId: the identity that publishes.text: 1 to 3,000 characters of plain text. Images, documents, mentions, company pages and scheduled comments are available in the app only.scheduledAt: an ISO 8601 date-time withZor an offset, at least 2 minutes and at most 365 days ahead.name: optional, the post’s name in Enginy. It defaults to the first line of the text, cut to 40 characters.reactionsAmount(0 to 500) andreactionTypes: reactions other identities add once the post is live.
scheduledAt, Enginy publishes the post from the identity’s LinkedIn account, and the identity’s owner gets a confirmation email when it is scheduled. The response (201) holds the publishingPostId, identityId, scheduledAt, name and appUrl. An identity outside your scope answers 404. A retry schedules a second post: after a 503, list the scheduled posts before you retry.
List and cancel scheduled posts
GET /v1/social-selling/scheduled-posts lists the posts waiting to be published, soonest first, with their publishingPostId, identityId, identityName, name, text, scheduledAt, reactionsAmount and appUrl. Filter with identityIds and with from / to on the publish time. Each takes an ISO date-time or a YYYY-MM-DD day (UTC: from starts at 00:00, to ends at 24:00). The window runs from now to 90 days ahead by default; without to, it ends 90 days after from (after now, for a from in the past). Page with page / pageSize (20 by default, at most 50).
scheduledAt is the live publish time. It is null when the post’s schedule no longer exists, so the post will not be published: cancel it. reactionsAmount is null when the schedule could not be read.
DELETE /v1/social-selling/scheduled-posts/{publishingPostId} stops the schedule and deletes the post, and returns { "publishingPostId": …, "cancelled": true }. A post that is already published or publishing, or that was not scheduled, cannot be cancelled (409). When the schedule cannot be stopped, the call answers 503 and keeps the post: retry it.