LeadShark API
ProProgrammatic 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).
https://apex.leadshark.ioAuthentication
Include your API key in the request header.
x-api-key— Your API keyGetting Your API Key
- Log in to your LeadShark account (any paid plan)
- Go to Settings → API Access
- 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.
- Call
GET /api/v1/posts(omitlinkedin_idfor your own posts) and pick the post you want to automate from theitemsarray. - Copy that item's
post_id(a URN likeurn:li:activity:7150…) andshare_url. - POST them to
/api/automationsaspost_idandlinkedin_post_urlrespectively.
# 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.datafor the current page of automations; useresponse.paginationfor metadata. - First page:
GET /api/automations?page=1&limit=10(or omit params for defaults). - Next page: same URL with
page=2, thenpage=3, etc. - Stop when
pagination.has_moreisfalse, or whenpage > pagination.total_pages. pagination.totalis the total number of automations across all pages.
| Field | Description |
|---|---|
total | Total automations (all pages) |
page | Current page number |
limit | Items per page requested |
total_pages | Total number of pages |
has_more | true if there is a next page |
stats object (same as dashboard)
| Field | Type | Description |
|---|---|---|
total_comments | number | Processed (GREATEST of webhooks, replies, chats with comment) |
total_dms_sent | number | Initial DMs sent |
total_connections_sent | number | Connection requests we sent (pending; outcome unknown) |
total_connections_accepted | number | Connection requests we accepted (inbound) |
total_comments_replied | number | First-degree comment replies sent |
total_non_first_degree_replies | number | Non-connected replies sent |
total_follow_ups_sent | number | Follow-up DMs sent |
total_follow_ups_skipped | number | Follow-ups skipped (e.g. no reply) |
total_auto_likes | number | Comments 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):
| Value | Who gets the follow-up |
|---|---|
off (default) | Everyone |
fit_or_adjacent | Leads labelled fit or maybe |
fit_only | Leads 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.
| Field | Status | Use instead |
|---|---|---|
auto_enrich | Deprecated — ignored until 2026-11-18, then rejected | On-demand enrichment: dashboard Enrich button or the MCP enrich tool |
icp_preset_id | Deprecated — ignored until 2026-11-18, then rejected | icp_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:
- Create a Page via
POST /api/v1/pagesand save thepage.id. - Create the automation with
links_enabled: true(see parameter above). - Create one or more lead-magnet links via
POST /api/v1/linkspassing the newautomation_idandpage_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}}@JohnLeads 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.datafor the current page; useresponse.pagination.has_moreto 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_negativeand related internal fields are not included.
Lead object — available fields
Each item in data includes these fields (when present):
| Field | Type | Description |
|---|---|---|
id | string (UUID) | Lead record ID |
name | string | Full name |
title | string | LinkedIn headline / job title |
linkedin_url | string | LinkedIn profile URL |
source | string | Automation or source name |
created_at | string (ISO 8601) | When the lead was captured |
updated_at | string (ISO 8601) | Last update time |
commenter_id | string | LinkedIn member URN (e.g. ACoAAB...) |
post_id | string | null | LinkedIn post ID if from a post |
linkedin_username | string | null | LinkedIn username |
first_name | string | null | First name (when available) |
icp_headline_fit | string | null | ICP 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_pct | number | null | ICP Fit match, 0–100 |
icp_headline_confidence | number | null | How readable the headline was, 0–100. Low means an ambiguous headline, not a bad lead. |
icp_headline_at | string | null | ISO timestamp of the headline read. null = the lead hasn't been read yet (usually ~10 min after capture). |
icp_score | number | null | Compatibility mirror of icp_headline_pct / 100 (0–1) |
lead_type | string | automation | post_engagement | connection_requests |
engagements | array | Engagement events (e.g. comments) |
icp_fit | string | null | Quiz outcome from an attached Quiz Page: fit | maybe | not. Unrelated to icp_headline_fit. |
archived | boolean | Whether the lead is archived |
enriched_profile | object | null | Full profile JSON from on-demand enrichment (dashboard Enrich button or the MCP enrich tool) |
enriched_at | string | null | When the lead was last enriched on demand |
email | string | null | Work 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
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:
| Field | Type | Required | Description |
|---|---|---|---|
name | string | required* | Automation name |
dm_template | string | required* | Primary DM template (max 2000 chars) |
keywords | string[] | optional | Trigger keywords (max 20) |
dm_templates | string[] | optional | Multiple DM templates (rotated) |
comment_reply_template | string[] | optional | Reply templates for comments |
non_first_degree_reply_template | string[] | optional | Reply for non-connections |
auto_connect | boolean | optional | Send connection requests (default: false) |
auto_like | boolean | optional | Auto-like all comments — Pro+/Apex only (default: false) |
icp_gate | string | optional | ICP Fit gate for follow-up DMs: off | fit_or_adjacent | fit_only — see icp_gate (default: off) |
auto_enrich | boolean | deprecated | Accepted and ignored until 2026-11-18, then rejected. Automatic background enrichment no longer exists — enrich on demand instead. |
enable_follow_up | boolean | optional | Enable follow-up DMs (default: false) |
follow_up_delay_minutes | number | optional | Delay before follow-up (default: 60) |
follow_up_only_if_no_response | boolean | optional | Only follow up if no reply (default: true) |
follow_up_template | string | optional | Follow-up message template |
follow_up_template_2 | string | optional | Second follow-up template — sent after the 1st follow-up — Pro+/Apex only (ignored on lower tiers) |
follow_up_delay_minutes_2 | number | optional | Delay for the 2nd follow-up, measured from when the 1st follow-up was sent — Pro+/Apex only (default: 120) |
links_enabled | boolean | optional | Enable link tracking — wraps URLs in DM templates with leads.sh tracking links (default: false) |
page_id | string | optional | Attach a Page to the automation. Pass the page UUID from POST /api/v1/pages. Requires links_enabled: true |
quiz_enabled | boolean | optional | Enable quiz questions on the attached page (default: false) |
template_id | string | optional | Use 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:
Note: PDF and video cannot be combined with other files. Only one PDF or video per post.
Error Codes
VALIDATION_ERROR — Invalid content, time, or automation configTIME_CONFLICT — Another post scheduled at this time (15-min window)SCHEDULING_TOO_SOON — Cannot edit post within 15 minutes of publish timePOST_ALREADY_PUBLISHED — Cannot modify published or failed postsGuide: 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.shtracking 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.
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
NewSearch posts by keyword
ApexNewLive 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.
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)
ApexOne 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.
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.
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_pagesto know how many pages exist. Incrementpageuntil you reach it. - Both endpoints use
total_pagespagination (nothas_more). Checkpage < total_pagesto 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:
| Field | Type | Description |
|---|---|---|
id | string (UUID) | Hot lead record ID |
actor_name | string | Lead's full name |
actor_linkedin_id | string | LinkedIn member identifier |
actor_linkedin_url | string | LinkedIn profile URL |
actor_profile_picture_url | string | null | Profile picture URL |
heat_score | number | Composite engagement score (higher = hotter lead) |
signal_count | number | Total number of signal events from this lead |
signal_breakdown | object | Counts per signal type, e.g. { "comment": 5, "reaction": 3 } |
top_signals | array | Recent signal events with type, date, and source_url |
connection_status | string | LinkedIn relationship: FIRST_DEGREE, SECOND_DEGREE, etc. |
first_seen_at | string (ISO 8601) | When this lead first appeared in signals |
computed_at | string (ISO 8601) | When the heat score was last computed |
Event object — available fields
Each item in events includes these fields:
| Field | Type | Description |
|---|---|---|
id | string (UUID) | Signal event record ID |
actor_linkedin_id | string | LinkedIn member identifier |
actor_name | string | Lead's full name |
actor_title | string | null | LinkedIn headline / job title |
actor_linkedin_url | string | LinkedIn profile URL |
signal_type | string | One of the signal types below |
signal_weight | number | Weight of this signal toward heat score |
source_url | string | null | LinkedIn post or page URL that triggered the signal |
meta | object | Additional metadata (varies by signal type — e.g. reaction_type for reactions, comment_text for comments) |
signal_date | string (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_at | string (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
| Type | Description |
|---|---|
comment | Commented on your post |
reaction | Reacted to your post |
repost | Reposted your content |
profile_view | Viewed your LinkedIn profile |
lead_magnet_click | Clicked your lead magnet link |
dm_sent | You sent them a DM |
comment_reply | You replied to their comment |
connection_accepted | Connection request accepted |
connection_sent | Connection request sent |
automation_engagement | Engagement 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_atday. They also havesignal_daterooted 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.
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
post_id in mind, want to count anonymous profile viewers, or need reaction_type for sentiment segmentation.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
| Code | Status | When |
|---|---|---|
invalid_post_id | 400 | post_id isn't a LinkedIn activity URN or numeric id |
invalid_cursor | 400 | cursor exceeds 4096 characters (reactions / comments only — real cursors are typically <200 chars) |
linkedin_not_connected | 400 | Workspace has no LinkedIn account connected |
linkedin_post_not_found | 404 | LinkedIn returned 404 for this post id (deleted, unauthorized, or wrong urn form) |
premium_required | 403 | /profile-viewers only — connected LinkedIn account isn't Premium (detected as zero results on first page) |
apex_required | 403 | /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
recentreads "started following you" notification cards (best for newest followers). Usesource=listonly when you need bulk export and can tolerate LIST limitations on large accounts. network_distancevalues:DISTANCE_1,DISTANCE_2,DISTANCE_3,OUT_OF_NETWORK,SELF, ornullwhen 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
- Who viewed your profile → GET /api/v1/profile-viewers (Apex)
- Who liked your posts → GET /api/v1/posts then /reactions per post
- Who commented → same post list, then /comments per post
- New followers → GET /api/v1/followers?source=recent (Apex)
403 premium_required otherwise). API key from Settings → API Keys.Four pulls, one report
| Data | Endpoints | Precise last-24h? |
|---|---|---|
| Comments | /api/v1/posts → /posts/{post_id}/comments | Yes — filter on posted_at (ISO 8601). |
| Likes | /api/v1/posts → /posts/{post_id}/reactions | No per-like timestamp — use daily snapshot diff (below). |
| Profile viewers | /api/v1/profile-viewers | viewed_at_label only — use snapshot diff. |
| New followers | /api/v1/followers?source=recent | No 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_id3. 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.)
- Paginate
GET /api/v1/posts?limit=50→ collectpost_idvalues (optionally stop whenposted_atis older than ~30 days). - Per post: paginate comments → keep
posted_at ≥ now() − 24h. - Per post: paginate reactions → build today's reactor ID set.
- Diff reactor sets vs yesterday → new likes.
- Paginate profile viewers → diff vs yesterday → new viewers.
- Paginate
/followers?source=recent→ diff vs yesterday → new followers. - Persist today's snapshots; send your report (email, Slack, sheet, warehouse).
Links APIPro+
Create and manage lead magnet links with analytics. Requires Pro+ or Apex subscription.
For event_type=click, email/name/phone are always null (clicks don't capture contact info). For event_type=email_capture, both email and commenter_id are populated together. Construct the LinkedIn profile URL as https://linkedin.com/in/{commenter_id}. When commenter_id is "anonymous", the visitor came through a generic share rather than a personalized redirect — there is no LinkedIn identity to join.
Pages APIPro+
Create and manage quiz pages, view responses, and export collected emails. Requires Pro+ or Apex subscription.
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 ashttps://linkedin.com/in/{commenter_id}whenlinkedin_urlis 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 (0–100) when the visitor completed a scored quiz on this page;nullotherwise.lead— joined LeadShark lead profile (name,title,linkedin_url) when we've seen this LinkedIn identity before;nullfor first-touch unknown profiles.source—email_gate(entered email on a gate) orquiz_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;nullfor legacy rows without stored attribution.
/links/:slug/eventsUse /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.
new_commentPro— new comment on a tracked postemail_capturedPro— email captured via personalized linklead_sentPro— lead manually sent from the Leads tablenew_profile_visitApex— LinkedIn profile viewnew_personal_followerApex— newly discovered personal profile followernew_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.
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 body401UnauthorizedMissing or invalid API key404Not FoundResource does not exist429Too Many RequestsRate limit exceeded500Server ErrorUnexpected error, try againNeed Help?
Contact our team for API support.
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.
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.
| channel | what it caps | cadence | max |
|---|---|---|---|
dm | Direct messages | daily | 250 |
connected_reply | Comment replies to 1st-degree connections (can’t go below your daily DM limit) | daily | 250 |
non_connected_reply | Comment replies to non-connections | daily | 250 |
connection_daily | Connection requests | daily | 500 |
connection_weekly | Connection requests | weekly | 1000 |
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.