LeadShark API

Pro

Programmatic access to LinkedIn enrichment, automations, and more.

Last updated: May 7, 2026

Overview

The LeadShark API provides programmatic access to LinkedIn enrichment, automation management, bookmarking, and scheduled posting. Available on all paid plans (Pro, Pro+, Apex).

Base URLhttps://apex.leadshark.io

Authentication

Include your API key in the request header.

Headerx-api-key— Your API key

Getting Your API Key

  1. Log in to your LeadShark account (any paid plan)
  2. Go to Settings → API Access
  3. Generate your API key

Rate Limits

250

req/hr

Hourly

6,000

req/day

Daily

100

req/min

Burst

Exceeding limits returns 429 Too Many Requests. Use exponential backoff.

Enrichment API

Enrich LinkedIn profiles and companies with detailed data.

Recommended daily volume

Each call performs a real profile view from your connected LinkedIn account. We recommend capping enrichment at ~200–250 profiles per day, spread across the day.

Soft guideline — not enforced by the API.

Automations API

Create and manage LinkedIn post engagement automations.

How to automate a specific post

Creating an automation requires the LinkedIn post's URN (the field post_id) and its share URL (linkedin_post_url). Both come straight out of GET /api/v1/posts.

  1. Call GET /api/v1/posts (omit linkedin_id for your own posts) and pick the post you want to automate from the items array.
  2. Copy that item's post_id (a URN like urn:li:activity:7150…) and share_url.
  3. POST them to /api/automations as post_id and linkedin_post_url respectively.
# 1. Find the post you want to automate
curl 'https://apex.leadshark.io/api/v1/posts?limit=5' \
  -H "x-api-key: YOUR_API_KEY"
# → grab items[N].post_id  (e.g. "urn:li:activity:7150123456789012345")
# → grab items[N].share_url (e.g. "https://www.linkedin.com/posts/...")

# 2. Create the automation using those values
curl -X POST 'https://apex.leadshark.io/api/automations' \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_API_KEY" \
  -d '{
    "name": "Guide Giveaway",
    "post_id": "urn:li:activity:7150123456789012345",
    "linkedin_post_url": "https://www.linkedin.com/posts/...",
    "keywords": ["interested"],
    "dm_template": "Hi {{firstName}}! Here is the guide: https://example.com/guide"
  }'

Format matters: use the full URN exactly as returned by /api/v1/posts. The webhook matcher compares automations against the LinkedIn URN; passing only the numeric tail (e.g. 7150…) creates the row but engagement events won't bind to it.

How to paginate

  • Use response.data for the current page of automations; use response.pagination for metadata.
  • First page: GET /api/automations?page=1&limit=10 (or omit params for defaults).
  • Next page: same URL with page=2, then page=3, etc.
  • Stop when pagination.has_more is false, or when page > pagination.total_pages.
  • pagination.total is the total number of automations across all pages.
FieldDescription
totalTotal automations (all pages)
pageCurrent page number
limitItems per page requested
total_pagesTotal number of pages
has_moretrue if there is a next page

stats object (same as dashboard)

FieldTypeDescription
total_commentsnumberProcessed (GREATEST of webhooks, replies, chats with comment)
total_dms_sentnumberInitial DMs sent
total_connections_sentnumberConnection requests we sent (pending; outcome unknown)
total_connections_acceptednumberConnection requests we accepted (inbound)
total_comments_repliednumberFirst-degree comment replies sent
total_non_first_degree_repliesnumberNon-connected replies sent
total_follow_ups_sentnumberFollow-up DMs sent
total_follow_ups_skippednumberFollow-ups skipped (e.g. no reply)
total_auto_likesnumberComments auto-liked

Aliases: processed, dms_sent, sent, accepted are also set for backward compatibility.

icp_gate — gate follow-up DMs on ICP Fit

Write a one-line, plain-language ideal customer profile in Settings → Ideal Customer. Every new lead's name and LinkedIn headline are read against it automatically (~10 minutes after capture, all plans, no enrichment needed) and labelled fit, maybe (shown as "Adjacent" in the dashboard) or not.icp_gate decides who receives the automation's follow-up DM (the initial DM, comment replies and connection requests are unaffected):

ValueWho gets the follow-up
off (default)Everyone
fit_or_adjacentLeads labelled fit or maybe
fit_onlyLeads labelled fit only
  • Leads with no usable headline are never blocked by the gate.
  • If a lead's label isn't ready when the follow-up is due, LeadShark holds the follow-up for up to 6 hours and then sends it anyway.
  • Any other value is rejected with 400 icp_gate must be one of: off, fit_or_adjacent, fit_only.
  • Read the labels back on each lead via GET /api/leads (icp_headline_fit, icp_headline_pct, …).

Deprecated: auto_enrich and icp_preset_id

The per-automation auto-enrich toggle and ICP presets have been replaced by ICP Fit + icp_gate. Both fields are accepted and ignored on POST / PUT /api/automations until 2026-11-18, after which requests that include them are rejected. They are no longer returned in list/get responses.

FieldStatusUse instead
auto_enrichDeprecated — ignored until 2026-11-18, then rejectedOn-demand enrichment: dashboard Enrich button or the MCP enrich tool
icp_preset_idDeprecated — ignored until 2026-11-18, then rejectedicp_gate + a one-line ICP in Settings → Ideal Customer

When a request still includes either field, the response carries a Deprecation: true header and a warnings array:

{
  "id": "auto-uuid-new",
  ...,
  "warnings": [
    "auto_enrich is deprecated and was ignored. Use icp_gate (off | fit_or_adjacent | fit_only) with a one-line ICP in Settings → Ideal Customer."
  ]
}

Attaching a Page (lead magnet) to an automation

Pages don't attach directly to automations — they attach to the lead-magnet links the automation generates. Three steps:

  1. Create a Page via POST /api/v1/pages and save the page.id.
  2. Create the automation with links_enabled: true (see parameter above).
  3. Create one or more lead-magnet links via POST /api/v1/links passing the new automation_id and page_id. Every link the automation surfaces in DMs/comments will route through that page.

Doing it all in one shot? Scheduled posts with a pre_automation object accept page_id directly — see Pages + Pre-Automation (Full Flow) for the end-to-end recipe.

Webhook-only automation (minimal payload)

Pass only the three required fields and skip everything else. The automation performs no LeadShark-side actions — no DMs, no comment replies, no connection requests — but every comment on the post still streams to your configured webhooks in real time. Use this when you want to handle engagement entirely in your own backend.

curl -X POST 'https://apex.leadshark.io/api/automations' \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_API_KEY" \
  -d '{
    "name": "Webhook only — Q1 lead magnet",
    "post_id": "urn:li:activity:7150123456789012345",
    "linkedin_post_url": "https://www.linkedin.com/posts/..."
  }'

Defaults applied: keywords: [], dm_template: "", auto_connect: false, auto_like: false, status: "Running". This is the same shape the auto-detect cron uses internally for posts you publish with Auto-Automate turned on.

Template Variables

{{firstName}}John
{{fullName}}John Doe
{{linkedinUsername}}john-doe
{{firstNameMention}}@John

Leads API

List all your leads (from automations and post engagement). Each lead includes its ICP Fit headline label and email (when present). Paginated for large lists.

  • Use response.data for the current page; use response.pagination.has_more to know if there is a next page.
  • First page: GET /api/leads?page=1&limit=250 (omit params for defaults).
  • Only non-archived, individual leads (LinkedIn profile IDs starting with ACo) are returned.
  • Response uses a limited field set; user_id, icp_starred, icp_negative and related internal fields are not included.

Lead object — available fields

Each item in data includes these fields (when present):

FieldTypeDescription
idstring (UUID)Lead record ID
namestringFull name
titlestringLinkedIn headline / job title
linkedin_urlstringLinkedIn profile URL
sourcestringAutomation or source name
created_atstring (ISO 8601)When the lead was captured
updated_atstring (ISO 8601)Last update time
commenter_idstringLinkedIn member URN (e.g. ACoAAB...)
post_idstring | nullLinkedIn post ID if from a post
linkedin_usernamestring | nullLinkedIn username
first_namestring | nullFirst name (when available)
icp_headline_fitstring | nullICP Fit label from the lead's LinkedIn headline vs. your one-line ICP (Settings → Ideal Customer): fit | maybe ("Adjacent" in the dashboard) | not. All plans. null until read.
icp_headline_pctnumber | nullICP Fit match, 0–100
icp_headline_confidencenumber | nullHow readable the headline was, 0–100. Low means an ambiguous headline, not a bad lead.
icp_headline_atstring | nullISO timestamp of the headline read. null = the lead hasn't been read yet (usually ~10 min after capture).
icp_scorenumber | nullCompatibility mirror of icp_headline_pct / 100 (0–1)
lead_typestringautomation | post_engagement | connection_requests
engagementsarrayEngagement events (e.g. comments)
icp_fitstring | nullQuiz outcome from an attached Quiz Page: fit | maybe | not. Unrelated to icp_headline_fit.
archivedbooleanWhether the lead is archived
enriched_profileobject | nullFull profile JSON from on-demand enrichment (dashboard Enrich button or the MCP enrich tool)
enriched_atstring | nullWhen the lead was last enriched on demand
emailstring | nullWork email — set when the MCP enrich tool ran with include_email (Apex, via your linked PersonEnrich account) or was found previously

Note: there is no automatic background enrichment. enriched_profile, enriched_at, and email are only populated by on-demand profile enrichment — the dashboard Enrich button or the MCP enrich tool (include_email is Apex and uses your linked PersonEnrich account). ICP Fit labels (icp_headline_*) need no enrichment and are computed on every plan.

Bookmarks API

Save and organize LinkedIn profiles with tags and notes.

Scheduled Posts API

Schedule LinkedIn posts with optional pre-configured automations that activate when the post goes live.

Scheduling Constraints

Minimum: 15 minutes from now
Maximum: 90 days in advance

Mention Anchors

When sending mentions, each mention must have exactly one clear anchor in content: an indexed placeholder like {{0}}, an exact @Full Name, or one bare exact Full Name. Missing or ambiguous anchors are rejected so names do not silently publish untagged.

Find your template_id

List every automation template on your account (including ones you created in the dashboard) so you can reuse an existing template in automation.template_id instead of defining a new one inline.

Here's the simplest call

No query params needed — returns the first 20 templates (default page size), newest first.

curl 'https://apex.leadshark.io/api/automation-templates' \
  -H "x-api-key: YOUR_API_KEY"

Reuse a template: copy any id from the response above and pass it as automation.template_id in POST /api/scheduled-posts. When template_id is provided, name and dm_template become optional — any other fields you send will patch the template in place.

Pre-Automation Object

When the scheduled post publishes, an automation is automatically created with these settings:

FieldTypeRequiredDescription
namestringrequired*Automation name
dm_templatestringrequired*Primary DM template (max 2000 chars)
keywordsstring[]optionalTrigger keywords (max 20)
dm_templatesstring[]optionalMultiple DM templates (rotated)
comment_reply_templatestring[]optionalReply templates for comments
non_first_degree_reply_templatestring[]optionalReply for non-connections
auto_connectbooleanoptionalSend connection requests (default: false)
auto_likebooleanoptionalAuto-like all comments — Pro+/Apex only (default: false)
icp_gatestringoptionalICP Fit gate for follow-up DMs: off | fit_or_adjacent | fit_only — see icp_gate (default: off)
auto_enrichbooleandeprecatedAccepted and ignored until 2026-11-18, then rejected. Automatic background enrichment no longer exists — enrich on demand instead.
enable_follow_upbooleanoptionalEnable follow-up DMs (default: false)
follow_up_delay_minutesnumberoptionalDelay before follow-up (default: 60)
follow_up_only_if_no_responsebooleanoptionalOnly follow up if no reply (default: true)
follow_up_templatestringoptionalFollow-up message template
follow_up_template_2stringoptionalSecond follow-up template — sent after the 1st follow-up — Pro+/Apex only (ignored on lower tiers)
follow_up_delay_minutes_2numberoptionalDelay for the 2nd follow-up, measured from when the 1st follow-up was sent — Pro+/Apex only (default: 120)
links_enabledbooleanoptionalEnable link tracking — wraps URLs in DM templates with leads.sh tracking links (default: false)
page_idstringoptionalAttach a Page to the automation. Pass the page UUID from POST /api/v1/pages. Requires links_enabled: true
quiz_enabledbooleanoptionalEnable quiz questions on the attached page (default: false)
template_idstringoptionalUse existing template (makes name/dm_template optional). Find IDs via GET /api/automation-templates

* Required unless template_id is provided

File Attachments

Use multipart/form-data to upload files with your post:

Images: JPG, PNG, GIF — 5MB each
PDF: 1 file only — 50MB
Video: MP4 only — 50MB

Note: PDF and video cannot be combined with other files. Only one PDF or video per post.

Error Codes

VALIDATION_ERRORInvalid content, time, or automation config
TIME_CONFLICTAnother post scheduled at this time (15-min window)
SCHEDULING_TOO_SOONCannot edit post within 15 minutes of publish time
POST_ALREADY_PUBLISHEDCannot modify published or failed posts

Guide: Pages + Pre-Automation (Full Flow)

Create a lead magnet page, then schedule a post with a pre-automation that has the page attached — all via the API. When the post publishes, everything activates automatically.

Step 1 — Create a Page

Use the Pages API to create and publish a page. Save the page.id from the response.

curl -X POST 'https://apex.leadshark.io/api/v1/pages' \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "LinkedIn Growth Playbook",
    "status": "published",
    "page_title": "Get Your Free Playbook",
    "page_description": "The exact strategies I used to grow to 100K followers",
    "questions": [{
      "question_text": "What is your biggest LinkedIn challenge?",
      "answers": [
        { "answer_text": "Getting more engagement", "mapped_outcome": "fit" },
        { "answer_text": "Growing my network", "mapped_outcome": "fit" },
        { "answer_text": "Just browsing", "mapped_outcome": "not" }
      ]
    }]
  }'

Response: { "page": { "id": "abc-123", ... } }

Step 2 — Schedule Post with Page Attached

Pass the page_id in the automation object with links_enabled: true.

curl -X POST 'https://apex.leadshark.io/api/scheduled-posts' \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Comment PLAYBOOK and I'\''ll DM it to you!",
    "scheduled_time": "2026-04-25T14:00:00Z",
    "automation": {
      "name": "Playbook Giveaway",
      "keywords": ["playbook", "interested", "yes"],
      "dm_template": "Hey {{firstName}}! Here is your playbook: https://example.com/playbook",
      "auto_connect": true,
      "links_enabled": true,
      "page_id": "abc-123",
      "quiz_enabled": true
    }
  }'

Step 3 — Automatic on Publish

When the scheduled time arrives, everything activates automatically:

  • Post publishes to LinkedIn
  • Automation starts — comments matching keywords trigger DMs and connection requests
  • URLs in DM templates are wrapped with leads.sh tracking links tied to your page
  • Visitors see your page (with quiz if enabled) before reaching the destination URL

You can also attach a page to an existing automation using the Links API with page_id.

Posts & Stats API

List recent posts (your own or anyone's) and pull aggregate engagement stats.

Tip: Each item's post_id is the LinkedIn post URN — pass it straight to /api/v1/posts/{post_id}/comments (and friends) for follow-up reads.

List recent posts

New

Search posts by keyword

ApexNew

Live LinkedIn content search — posts that contain your keywords, by anyone. Distinct from GET /api/v1/posts (a known person's recent posts), GET /api/v1/discover (Lead Magnet Radar corpus), and POST /api/linkedin-search (people only). Each result's post_id drops into the engagement and comment/react endpoints.

Deprecated

GET /api/post-stats (list mode) is now superseded by GET /api/v1/posts. Existing callers continue to work — the response carries a Deprecation: true header and a Link header pointing at the successor. Migrate at your convenience. Summary mode (?summary=true) is unaffected and remains the canonical home for aggregate post metrics — see below.

Aggregate totals (summary mode)

Apex

One call returns lifetime totals across your last N posts (impressions, comments, reactions, reposts) without you having to paginate and sum yourself. Great for dashboards and weekly reports. Apex-gated because each call may walk multiple upstream pages.

When truncated is true: you hit max_posts before reaching the end of your post history. Pass next_cursor back as cursor to continue scanning — but in practice 500 covers nearly everyone.

Dashboard activity

Rollups of your automation activity for exports and reporting — comments processed, DMs, connections, replies, and leads — matching the main dashboard. Pick a granularity: daily, weekly, monthly, or all-time.

Date range: after counts from that moment onward; before stops before that instant (so use the start of the day after your last day to include a full calendar day). Optional cutoff returns two slices — before and on/after — for side-by-side comparisons. For granularity=all_time the window params are ignored.

Discover APIApex

A continuously refreshed feed of fresh lead-magnet posts from the LeadShark Lead Magnet Radar — prospect-rich posts to engage. Returns the public post, creator identity, and engagement counts only. Requires an Apex subscription.

Signals APIApex

Access your ranked hot leads and raw engagement signal events via API. Requires an Apex subscription.

  • Use response.pagination.total_pages to know how many pages exist. Increment page until you reach it.
  • Both endpoints use total_pages pagination (not has_more). Check page < total_pages to determine if more data is available.
  • All results are scoped to the authenticated user's data only.

Signal object — available fields

Each item in signals includes these fields:

FieldTypeDescription
idstring (UUID)Hot lead record ID
actor_namestringLead's full name
actor_linkedin_idstringLinkedIn member identifier
actor_linkedin_urlstringLinkedIn profile URL
actor_profile_picture_urlstring | nullProfile picture URL
heat_scorenumberComposite engagement score (higher = hotter lead)
signal_countnumberTotal number of signal events from this lead
signal_breakdownobjectCounts per signal type, e.g. { "comment": 5, "reaction": 3 }
top_signalsarrayRecent signal events with type, date, and source_url
connection_statusstringLinkedIn relationship: FIRST_DEGREE, SECOND_DEGREE, etc.
first_seen_atstring (ISO 8601)When this lead first appeared in signals
computed_atstring (ISO 8601)When the heat score was last computed

Event object — available fields

Each item in events includes these fields:

FieldTypeDescription
idstring (UUID)Signal event record ID
actor_linkedin_idstringLinkedIn member identifier
actor_namestringLead's full name
actor_titlestring | nullLinkedIn headline / job title
actor_linkedin_urlstringLinkedIn profile URL
signal_typestringOne of the signal types below
signal_weightnumberWeight of this signal toward heat score
source_urlstring | nullLinkedIn post or page URL that triggered the signal
metaobjectAdditional metadata (varies by signal type — e.g. reaction_type for reactions, comment_text for comments)
signal_datestring (ISO 8601)When the engagement happened on LinkedIn. Day-truncated. For comments / DMs / clicks / connections this is the actual event date. For reactions and reposts, LinkedIn does not surface a per-actor timestamp, so this is the day we first detected the actor (or, for historical pre-Apr 2026 rows, the post's publish date). Filterable via since / until.
created_atstring (ISO 8601)When the row was first inserted into our database. Filterable via since_captured / until_captured. Use this when you want "what's new this week" for reactions/reposts.

Signal Types

TypeDescription
commentCommented on your post
reactionReacted to your post
repostReposted your content
profile_viewViewed your LinkedIn profile
lead_magnet_clickClicked your lead magnet link
dm_sentYou sent them a DM
comment_replyYou replied to their comment
connection_acceptedConnection request accepted
connection_sentConnection request sent
automation_engagementEngagement via automation

Picking the right date filter

Most engagement types come from sources that include a per-event timestamp (LinkedIn comments, your DMs, your link clicks, connection requests). For those, signal_date is the actual event date and since / until behave intuitively.

Reactions and reposts are different. LinkedIn does not expose when each individual person reacted or reposted — only the current list of who has done so. We diff that list daily and stamp newly-detected rows with the day we first saw them, which is the most truthful approximation we can give.

Practical guidance:

  • "Show me everyone who engaged with my recent posts" → use since (filters by post / engagement date).
  • "Show me what new engagement I've received this week" → use since_captured (filters by when our system detected it). This is what you almost always want for reactions and reposts, and it's safe for every other signal type too.
  • Historical reactions imported during your account's initial signals backfill share the same created_at day. They also have signal_date rooted near the original post's publish day.

Engagement APIPro & up

See exactly who's engaging with you on LinkedIn — the people who reacted to a specific post, left a comment, reshared it, or recently viewed your profile. Results are pulled live from your connected LinkedIn account, so what you see is what's on LinkedIn right now.Popular pattern: compile a daily engagement digest (profile viewers, likes, comments, new followers) with a morning cron job.

Where do I get a post_id?

Call /api/post-stats, pick any post, and use its social_id — that's the post_id for these endpoints.

"social_id": "urn:li:ugcPost:74549342603287*****"   →   use as post_id
When to use this vs. the Signals API: the Signals API is a deduped, scored roll-up across all your tracked posts (use it for ICP-fit triage and heat-score ranking). These endpoints are the per-engagement, per-post slice — use them when you have a specific post_id in mind, want to count anonymous profile viewers, or need reaction_type for sentiment segmentation.
Apex-only endpoints. Profile-visitor and personal-follower data are gated to Apex across the entire surface — the new.profile.visit and new.personal_follower webhooks, the matching signals slices, and these polling endpoints all require an Apex subscription. Pro / Pro+ keys receive 403 with { "error": "This API requires a LeadShark Apex subscription" }.

Common envelope & pagination

Reactions, comments, reposts, and profile viewers share one envelope. Followers adds source and may include follower_count on the first page. To fetch the next page, pass ?cursor=<value>. When has_more is false you've reached the end.

{
  "items": [...],
  "pagination": {
    "cursor": "abc123" | null,
    "has_more": true | false
  }
}

Endpoint-specific error codes

CodeStatusWhen
invalid_post_id400post_id isn't a LinkedIn activity URN or numeric id
invalid_cursor400cursor exceeds 4096 characters (reactions / comments only — real cursors are typically <200 chars)
linkedin_not_connected400Workspace has no LinkedIn account connected
linkedin_post_not_found404LinkedIn returned 404 for this post id (deleted, unauthorized, or wrong urn form)
premium_required403/profile-viewers only — connected LinkedIn account isn't Premium (detected as zero results on first page)
apex_required403/profile-viewers and /followers — API key's subscription is not Apex (the body says "This API requires a LeadShark Apex subscription"). Reactions / comments / reposts still accept Pro & up.
  • Reposts pagination uses single-page-per-call (~10 items) deliberately. The underlying LinkedIn surface is sensitive to aggressive polling; spread your walks with a 1–2 second delay between cursor calls.
  • Profile viewers Premium gate hard-fails on the first page only. Subsequent calls with a valid cursor that return zero elements are returned as a normal empty page with has_more: false.
  • Followers source — default recent reads "started following you" notification cards (best for newest followers). Use source=list only when you need bulk export and can tolerate LIST limitations on large accounts.
  • network_distance values: DISTANCE_1, DISTANCE_2, DISTANCE_3, OUT_OF_NETWORK, SELF, or null when LinkedIn doesn't expose it.

Daily engagement digestPlaybook

A popular integration pattern: run one scheduled job every morning (e.g. 8:00 in your timezone) and compile who engaged with you in the last 24 hours — profile viewers, post likes, comments, and new followers — using only the Engagement API endpoints above (live reads from your connected LinkedIn account).

What you pull each morning

Plan requirements: Apex for profile viewers and followers. Pro+ or Apex for listing posts and per-post comments/reactions. LinkedIn Premium on the connected account for profile viewers (403 premium_required otherwise). API key from Settings → API Keys.

Four pulls, one report

DataEndpointsPrecise last-24h?
Comments/api/v1/posts/posts/{post_id}/commentsYes — filter on posted_at (ISO 8601).
Likes/api/v1/posts/posts/{post_id}/reactionsNo per-like timestamp — use daily snapshot diff (below).
Profile viewers/api/v1/profile-viewersviewed_at_label only — use snapshot diff.
New followers/api/v1/followers?source=recentNo followed_at — use snapshot diff.

1. Comments — filter on posted_at

List posts, then pull comments per post_id. Paginate both steps. This is the only engagement type with a true 24-hour window on a single daily poll.

const since = Date.now() - 24 * 60 * 60 * 1000;
const last24h = items.filter(
  (c) => c.posted_at && Date.parse(c.posted_at) >= since
);

2. Likes — daily snapshot diff

The reactions endpoint returns reaction_type per person but not when they reacted. Each morning, store the full set of reactor.member_urn (or reactor.linkedin_id) per post. New IDs since yesterday ≈ likes in the last run.

NEW_LIKES = reactors_today − reactors_yesterday   // per post_id

3. Profile viewers — daily snapshot diff

Paginate /profile-viewers (10 per page, ?cursor=10, 20, …). Diff viewer keys day over day. Parsing viewed_at_label ("1d ago") is possible but brittle.

4. New followers — daily snapshot diff

source=recent reads "started following you" notification cards — best for newest followers on large accounts. Paginate with pagination.cursor. Diff actor_id day over day.

Job outline (cron, GitHub Actions, Zapier, etc.)

  1. Paginate GET /api/v1/posts?limit=50 → collect post_id values (optionally stop when posted_at is older than ~30 days).
  2. Per post: paginate comments → keep posted_at ≥ now() − 24h.
  3. Per post: paginate reactions → build today's reactor ID set.
  4. Diff reactor sets vs yesterday → new likes.
  5. Paginate profile viewers → diff vs yesterday → new viewers.
  6. Paginate /followers?source=recent → diff vs yesterday → new followers.
  7. Persist today's snapshots; send your report (email, Slack, sheet, warehouse).
Rate limits: a once-daily job with pagination is usually well within Apex limits (250/hour, 6,000/day). Very large accounts may need a narrower post window or a 1–2s delay between paginated calls on profile viewers and reposts.
Other paths: if you prefer push delivery or "when LeadShark first detected it" filtering, see the Webhooks API and Signals events endpoints. This playbook stays on raw Engagement reads only.

Pages APIPro+

Create and manage quiz pages, view responses, and export collected emails. Requires Pro+ or Apex subscription.

Field reference
  • email / name / phone — captured directly on the form.
  • commenter_id — LinkedIn vanity slug of the visitor (when the link was personalized). Build the profile URL as https://linkedin.com/in/{commenter_id} when linkedin_url is missing.
  • linkedin_url — joined from the lead profile when available; falls back to the click event's metadata.
  • country — IP-derived country at capture time. Useful for geo-disqualification before paid enrichment.
  • engagement_score — quiz response engagement score (0100) when the visitor completed a scored quiz on this page; null otherwise.
  • lead — joined LeadShark lead profile (name, title, linkedin_url) when we've seen this LinkedIn identity before; null for first-touch unknown profiles.
  • sourceemail_gate (entered email on a gate) or quiz_response (submitted via quiz flow).
  • link — the originating lead-magnet link this lead was captured through (id, slug, url) when the capture can be attributed to a specific link; null for legacy rows without stored attribution.
When to use this endpoint vs. /links/:slug/events

Use /pages/:id/emails when you want a single deduped roll-up of every email captured for a lead magnet across all of its links — ideal for one-shot exports into a CRM. Use /links/:slug/events?event_type=email_capture when you want the raw event stream per link with timestamps and pagination — ideal for incremental ingestion. Both endpoints surface commenter_idfor joining captures back to LinkedIn identities. Construct the profile URL as https://linkedin.com/in/{commenter_id}when linkedin_url is null.

Webhooks APIPro

Programmatically create, update, list, delete, and test webhooks. Webhooks let LeadShark push real-time events (new comments, likes, profile visits, captured emails, etc.) to your own URL with an HMAC-SHA256 signature. Available on Pro and above; some event types are Apex-only.

Event types & tier requirements
  • new_commentPro— new comment on a tracked post
  • email_capturedPro— email captured via personalized link
  • lead_sentPro— lead manually sent from the Leads table
  • new_profile_visitApex— LinkedIn profile view
  • new_personal_followerApex— newly discovered personal profile follower
  • new_likeApex— new reaction on a tracked post

Pro / Pro+ accounts can create webhooks for the three Pro events. Including new_profile_visit, new_personal_follower, or new_like in event_types from a non-Apex account returns 403 with apex_only_event_types.

Signature verification

Every event includes X-Webhook-Signature (HMAC-SHA256 of the raw body, hex encoded) andX-Webhook-Event (e.g. new.comment). Verify it using your webhook secret:

// Node.js example
const crypto = require('crypto');
const expected = crypto
  .createHmac('sha256', process.env.WEBHOOK_SECRET)
  .update(rawBody)
  .digest('hex');
const ok = crypto.timingSafeEqual(
  Buffer.from(expected),
  Buffer.from(req.headers['x-webhook-signature'])
);

The secret is only returned once — at creation time, or when you rotate it via PUT with rotate_secret: true. Store it securely.

Error Codes

400Bad RequestInvalid parameters or malformed body
401UnauthorizedMissing or invalid API key
404Not FoundResource does not exist
429Too Many RequestsRate limit exceeded
500Server ErrorUnexpected error, try again

Need Help?

Contact our team for API support.

Contact

LinkedIn Post Actions

Available to Pro, Pro+, and Apex accounts. These endpoints act through your connected LinkedIn account and use the same safety mode, queue, Emergency Stop, quiet-hours, pacing, retry ceiling, circuit breaker, and action log as Apex MCP. Free and unverifiable subscriptions fail closed before LinkedIn is called.

Every refused, queued, successful, or failed attempt is visible in the action log. Common refusal codes include scope_denied, mode_too_low,emergency_stop, emergency_stop_unverified, off_hours,cooldown, retry_ceiling, circuit_open,linkedin_rejected, and linkedin_auth_lost.

Activity Limits

Read where you stand — “can I act now, and how much room is left?” — and set any channel from 0 up to its max. With a direct API key it’s simple: raising and lowering both apply immediately, and the per-channel max is the safety bound. This is the REST twin of themanage_activity_limits MCP tool (which keeps a stricter guard for autonomous agents). Available to Pro, Pro+, and Apex. All caps are shared with your automations.

channelwhat it capscadencemax
dmDirect messagesdaily250
connected_replyComment replies to 1st-degree connections (can’t go below your daily DM limit)daily250
non_connected_replyComment replies to non-connectionsdaily250
connection_dailyConnection requestsdaily500
connection_weeklyConnection requestsweekly1000
Notes: DM and reply caps are daily; connection caps are daily and weekly(there is no weekly DM cap). Values above a channel’s max are clamped to the max.Connected comment replies can’t be set below your daily DM limit — a below-floor write returns below_dm_floor; lower the DM limit first (or set the two together). Every change is written to your action log. Refusal codes you might see: invalid_channel,invalid_limit, below_dm_floor, pro_required.