curl --request POST \
--url https://api.fetchin.io/api/v1/posts/search \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <api-key>' \
--data '
{
"query": "\"head of growth\" AND (saas OR fintech)",
"postedWithin": "week",
"sortBy": "date",
"maxResults": 25
}
'{
"query": "\"head of growth\" AND (saas OR fintech)",
"sort": null,
"results": [
{
"id": "urn:li:activity:7486820978292072449",
"entityId": "7486820978292072449",
"shareUrn": "urn:li:ugcPost:7486820977411145728",
"shareUrl": "https://www.linkedin.com/feed/update/urn:li:activity:7486820978292072449/",
"date": "2026-10-04T16:33:39.632Z",
"postedAgoShort": "3d",
"isEdited": false,
"content": "We just hired our first Head of Growth. Three things I wish I had known before writing the job description for a SaaS company this size...",
"postType": "image",
"media": [
{
"type": "image",
"url": "https://media.licdn.com/dms/image/v2/D4E22AQ.../feedshare-shrink_2048_1536/0/1759590819000?e=1762992000&v=beta&t=...",
"width": 2048,
"height": 1365,
"aspectRatio": 1.5
}
],
"images": [
{
"url": "https://media.licdn.com/dms/image/v2/D4E22AQ.../feedshare-shrink_2048_1536/0/1759590819000?e=1762992000&v=beta&t=...",
"width": 2048,
"height": 1365
}
],
"imageUrl": "https://media.licdn.com/dms/image/v2/D4E22AQ.../feedshare-shrink_2048_1536/0/1759590819000?e=1762992000&v=beta&t=...",
"author": {
"type": "profile",
"id": "ACoAAA8BYqEBCGLg_vT_ca6mMEqkpp9nVffJ3hc",
"name": "Marie Dupont",
"url": "https://www.linkedin.com/in/marie-dupont",
"publicIdentifier": "marie-dupont",
"info": "Co-founder & CEO at Northbound",
"headline": "Co-founder & CEO at Northbound",
"avatar": {
"url": "https://media.licdn.com/dms/image/v2/D4E03AQ.../profile-displayphoto-shrink_400_400/0/1726000000000?e=1762992000&v=beta&t=...",
"width": 400,
"height": 400
}
},
"reactionCount": 412,
"commentCount": 37,
"sharesCount": 8,
"reactionTypeCounts": [
{ "type": "LIKE", "count": 351 },
{ "type": "PRAISE", "count": 33 },
{ "type": "EMPATHY", "count": 12 },
{ "type": "INTEREST", "count": 9 },
{ "type": "APPRECIATION", "count": 7 },
{ "type": "ENTERTAINMENT", "count": 0 },
{ "type": "SUPER_LIKE", "count": 0 }
],
"reactionsByType": {
"like": 351,
"appreciation": 7,
"empathy": 12,
"interest": 9,
"praise": 33
},
"mentions": [
{
"type": "company",
"name": "Northbound",
"url": "https://www.linkedin.com/company/northbound"
}
],
"hashtags": ["hiring", "saas"],
"outboundLinks": ["https://northbound.example.com/careers"],
"isRepost": false,
"commentingDisabled": false
},
{
"id": "urn:li:activity:7489894675928027136",
"entityId": "7489894675928027136",
"shareUrn": "urn:li:share:7489894675324166145",
"shareUrl": "https://www.linkedin.com/feed/update/urn:li:activity:7489894675928027136/",
"date": "2026-10-06T04:07:26.255Z",
"postedAgoShort": "1d",
"isEdited": true,
"content": "This matches what we see on the fintech side too.",
"postType": "text",
"media": [],
"author": {
"type": "company",
"id": "1035",
"name": "Northbound",
"url": "https://www.linkedin.com/company/northbound",
"universalName": "northbound",
"info": "12,431 followers"
},
"reactionCount": 64,
"commentCount": 3,
"sharesCount": 1,
"reactionTypeCounts": [
{ "type": "LIKE", "count": 58 },
{ "type": "PRAISE", "count": 4 },
{ "type": "EMPATHY", "count": 1 },
{ "type": "INTEREST", "count": 1 },
{ "type": "APPRECIATION", "count": 0 },
{ "type": "ENTERTAINMENT", "count": 0 },
{ "type": "SUPER_LIKE", "count": 0 }
],
"reactionsByType": {
"like": 58,
"appreciation": 0,
"empathy": 1,
"interest": 1,
"praise": 4
},
"mentions": [],
"hashtags": [],
"outboundLinks": [],
"isRepost": true,
"resharedPost": {
"id": "urn:li:activity:7486820978292072449",
"shareUrn": "urn:li:ugcPost:7486820977411145728",
"date": "2026-10-04T16:33:39.632Z",
"author": {
"type": "profile",
"id": "ACoAAA8BYqEBCGLg_vT_ca6mMEqkpp9nVffJ3hc",
"name": "Marie Dupont",
"url": "https://www.linkedin.com/in/marie-dupont",
"publicIdentifier": "marie-dupont",
"headline": "Co-founder & CEO at Northbound"
},
"content": "We just hired our first Head of Growth. Three things I wish I had known before writing the job description for a SaaS company this size...",
"hashtags": ["hiring", "saas"]
}
}
],
"cursor": "eyJ2IjoxLCJxIjoiOWYyYzFlN2E0YjhkMzA1NiIsImYiOiJjMWE0ZTkwYjdkMzI2ODU0IiwiaSI6NTAsIm4iOjUwLCJjIjowfQ"
}
{
"query": "\"quantum pastry logistics\"",
"sort": null,
"results": [],
"cursor": null
}
{
"error": "'query' uses too many boolean operators: 6 (maximum 5). Split it into several searches and combine the results.",
"code": "INVALID_PARAMETER",
"details": {
"operators": 6,
"maxOperators": 5
}
}
{
"error": "'contentType[0]' must be one of: image, video, liveVideo, jobPost, document (received 'pdf').",
"code": "INVALID_PARAMETER"
}
{
"error": "Invalid API key",
"code": "INVALID_API_KEY"
}
{
"error": "Quota exceeded. Upgrade your plan or wait for renewal.",
"code": "QUOTA_EXHAUSTED",
"details": {
"creditsRemaining": 0,
"subscriptionCreditsRemaining": 0,
"paygCreditsRemaining": 0,
"buyCreditsUrl": "https://fetchin.io/credits"
}
}
{
"error": "Service temporarily unable to serve this request. Please retry.",
"code": "SERVICE_UNAVAILABLE"
}
Search Posts
Search public professional posts by keyword and filters, with a boolean query grammar and cursor pagination
curl --request POST \
--url https://api.fetchin.io/api/v1/posts/search \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <api-key>' \
--data '
{
"query": "\"head of growth\" AND (saas OR fintech)",
"postedWithin": "week",
"sortBy": "date",
"maxResults": 25
}
'{
"query": "\"head of growth\" AND (saas OR fintech)",
"sort": null,
"results": [
{
"id": "urn:li:activity:7486820978292072449",
"entityId": "7486820978292072449",
"shareUrn": "urn:li:ugcPost:7486820977411145728",
"shareUrl": "https://www.linkedin.com/feed/update/urn:li:activity:7486820978292072449/",
"date": "2026-10-04T16:33:39.632Z",
"postedAgoShort": "3d",
"isEdited": false,
"content": "We just hired our first Head of Growth. Three things I wish I had known before writing the job description for a SaaS company this size...",
"postType": "image",
"media": [
{
"type": "image",
"url": "https://media.licdn.com/dms/image/v2/D4E22AQ.../feedshare-shrink_2048_1536/0/1759590819000?e=1762992000&v=beta&t=...",
"width": 2048,
"height": 1365,
"aspectRatio": 1.5
}
],
"images": [
{
"url": "https://media.licdn.com/dms/image/v2/D4E22AQ.../feedshare-shrink_2048_1536/0/1759590819000?e=1762992000&v=beta&t=...",
"width": 2048,
"height": 1365
}
],
"imageUrl": "https://media.licdn.com/dms/image/v2/D4E22AQ.../feedshare-shrink_2048_1536/0/1759590819000?e=1762992000&v=beta&t=...",
"author": {
"type": "profile",
"id": "ACoAAA8BYqEBCGLg_vT_ca6mMEqkpp9nVffJ3hc",
"name": "Marie Dupont",
"url": "https://www.linkedin.com/in/marie-dupont",
"publicIdentifier": "marie-dupont",
"info": "Co-founder & CEO at Northbound",
"headline": "Co-founder & CEO at Northbound",
"avatar": {
"url": "https://media.licdn.com/dms/image/v2/D4E03AQ.../profile-displayphoto-shrink_400_400/0/1726000000000?e=1762992000&v=beta&t=...",
"width": 400,
"height": 400
}
},
"reactionCount": 412,
"commentCount": 37,
"sharesCount": 8,
"reactionTypeCounts": [
{ "type": "LIKE", "count": 351 },
{ "type": "PRAISE", "count": 33 },
{ "type": "EMPATHY", "count": 12 },
{ "type": "INTEREST", "count": 9 },
{ "type": "APPRECIATION", "count": 7 },
{ "type": "ENTERTAINMENT", "count": 0 },
{ "type": "SUPER_LIKE", "count": 0 }
],
"reactionsByType": {
"like": 351,
"appreciation": 7,
"empathy": 12,
"interest": 9,
"praise": 33
},
"mentions": [
{
"type": "company",
"name": "Northbound",
"url": "https://www.linkedin.com/company/northbound"
}
],
"hashtags": ["hiring", "saas"],
"outboundLinks": ["https://northbound.example.com/careers"],
"isRepost": false,
"commentingDisabled": false
},
{
"id": "urn:li:activity:7489894675928027136",
"entityId": "7489894675928027136",
"shareUrn": "urn:li:share:7489894675324166145",
"shareUrl": "https://www.linkedin.com/feed/update/urn:li:activity:7489894675928027136/",
"date": "2026-10-06T04:07:26.255Z",
"postedAgoShort": "1d",
"isEdited": true,
"content": "This matches what we see on the fintech side too.",
"postType": "text",
"media": [],
"author": {
"type": "company",
"id": "1035",
"name": "Northbound",
"url": "https://www.linkedin.com/company/northbound",
"universalName": "northbound",
"info": "12,431 followers"
},
"reactionCount": 64,
"commentCount": 3,
"sharesCount": 1,
"reactionTypeCounts": [
{ "type": "LIKE", "count": 58 },
{ "type": "PRAISE", "count": 4 },
{ "type": "EMPATHY", "count": 1 },
{ "type": "INTEREST", "count": 1 },
{ "type": "APPRECIATION", "count": 0 },
{ "type": "ENTERTAINMENT", "count": 0 },
{ "type": "SUPER_LIKE", "count": 0 }
],
"reactionsByType": {
"like": 58,
"appreciation": 0,
"empathy": 1,
"interest": 1,
"praise": 4
},
"mentions": [],
"hashtags": [],
"outboundLinks": [],
"isRepost": true,
"resharedPost": {
"id": "urn:li:activity:7486820978292072449",
"shareUrn": "urn:li:ugcPost:7486820977411145728",
"date": "2026-10-04T16:33:39.632Z",
"author": {
"type": "profile",
"id": "ACoAAA8BYqEBCGLg_vT_ca6mMEqkpp9nVffJ3hc",
"name": "Marie Dupont",
"url": "https://www.linkedin.com/in/marie-dupont",
"publicIdentifier": "marie-dupont",
"headline": "Co-founder & CEO at Northbound"
},
"content": "We just hired our first Head of Growth. Three things I wish I had known before writing the job description for a SaaS company this size...",
"hashtags": ["hiring", "saas"]
}
}
],
"cursor": "eyJ2IjoxLCJxIjoiOWYyYzFlN2E0YjhkMzA1NiIsImYiOiJjMWE0ZTkwYjdkMzI2ODU0IiwiaSI6NTAsIm4iOjUwLCJjIjowfQ"
}
{
"query": "\"quantum pastry logistics\"",
"sort": null,
"results": [],
"cursor": null
}
{
"error": "'query' uses too many boolean operators: 6 (maximum 5). Split it into several searches and combine the results.",
"code": "INVALID_PARAMETER",
"details": {
"operators": 6,
"maxOperators": 5
}
}
{
"error": "'contentType[0]' must be one of: image, video, liveVideo, jobPost, document (received 'pdf').",
"code": "INVALID_PARAMETER"
}
{
"error": "Invalid API key",
"code": "INVALID_API_KEY"
}
{
"error": "Quota exceeded. Upgrade your plan or wait for renewal.",
"code": "QUOTA_EXHAUSTED",
"details": {
"creditsRemaining": 0,
"subscriptionCreditsRemaining": 0,
"paygCreditsRemaining": 0,
"buyCreditsUrl": "https://fetchin.io/credits"
}
}
{
"error": "Service temporarily unable to serve this request. Please retry.",
"code": "SERVICE_UNAVAILABLE"
}
Endpoint
POST /api/v1/posts/search
GET /api/v1/post/comments or
GET /api/v1/post/reactions on the
ones whose engagement you want in full.
query, or at least
one of authorProfiles / authorCompanies — a search with no words and no
author is a request for the whole network and is rejected with 400.Authentication
Include your API key in the request header:X-API-Key: your-api-key-here
Billing
- 1 credit whether the page comes back with 50 results, 1 result or none at all. The query ran either way, and an empty page is a real answer: it tells you nothing matches.
- The price does not change with
maxResults, so a 50-result page costs the same as a 1-result page. Ask for 50. 400,401,402,429and503are free. Every validation error is decided before the search runs, so a malformed body never costs a credit.- Each page of a cursor walk is its own call and its own credit.
- A post that repeats across two pages (see Pagination) is part of a page you already paid for; there is no per-result charge either way.
Request body
Filters AND together; the values inside one list filter OR.null
anywhere means “field omitted”.
maxResults above 50 are all 400s that
name the field — never a quiet drop.This matters more here than it looks. The underlying search ignores a value it
does not recognise and answers normally, so a single typo in contentType
would come back as a perfectly healthy page that silently applied no filter
at all, and you would have paid for it without any way to notice. Refusing
for free is the only honest option.authorProfiles or authorCompanies.relevance or date (newest first). There is no default.Omit it and the underlying search applies its own ordering. We do not label an
order we do not control, so the response reports sort: null in that case
rather than guessing a name for it.Two measured caveats before you reach for date:- it cannot be combined with a hashtag term in
query— the two do not compose upstream and return nothing at all, so the combination is refused with400instead; - on a tight window it narrows hard.
"hiring"over 24 hours returned 6 results unsorted and 1 withsortBy: "date". If you want coverage, omit the sort and sort the results yourself ondate.
24h, week or month. Keeps only posts published inside that window.This is applied by the search itself, so it changes which results exist, not
just which ones you see.2026-10-01T00:00:00Z) or an epoch in milliseconds. Must not be in the future
and at most 365 days ago.Unlike postedWithin, this one is applied on our side after the search runs.
A page trimmed by it comes back short and still costs its 1 credit. Use
postedWithin to pick the window and postedAfter to cut inside it.image, video, liveVideo, jobPost, document.ACoAA…) or any professional profile URL containing one.A slug on its own is not accepted here: resolving one would cost an extra fetch
per value. Resolve it once with
GET /api/v1/profile and pass the id.Can be sent without query, to page everything a set of people posted.query.400 rather than
billed for an empty page. The price is the same at 1 and at 50.This is a ceiling, not a promise. A page can come back with fewer results
than you asked for because results are de-duplicated by post id within the page
— a 50-result request measured 49 in production — or simply because it is the
last page. A short page is not an error.Not supported
Filters whose effect we could not confirm
Filters whose effect we could not confirm
400 instead
of offered, because an unrecognised filter is not refused by the underlying
search but silently ignored — so it would hand you a perfectly plausible,
completely unfiltered page that you paid for and have no way to detect.Group content
Group content
group is rejected with 400. Group posts are a separate surface with no
equivalent in this search.Anything relative to the caller
Anything relative to the caller
Collaborative articles
Collaborative articles
contentType value yet. The supported values are image, video,
liveVideo, jobPost and document.Query grammar
query is a boolean expression over words and phrases.
| Form | Example | Meaning |
|---|---|---|
| Bare term | hiring | The word, stemmed by the underlying search |
| Quoted phrase | "head of growth" | The words in that order |
AND | saas AND pricing | Both |
OR | saas OR fintech | Either |
NOT | hiring NOT intern | Exclude |
| Parentheses | (saas OR fintech) AND hiring | Grouping, nested up to 5 levels |
| Hashtag term | #hiring | The hashtag |
AND / OR / NOT
needs a term on both sides, and a query that only excludes
(NOT intern) is rejected — there would be nothing to search for.
The 5-operator limit
{
"error": "'query' uses too many boolean operators: 6 (maximum 5). Split it into several searches and combine the results.",
"code": "INVALID_PARAMETER",
"details": { "operators": 6, "maxOperators": 5 }
}
id. Splitting on the top-level OR branches is
usually the cheapest cut.AND, OR and NOT counts as one. Parentheses, phrases and bare terms
count as none, so ("head of growth" OR "vp growth") AND (saas OR fintech) uses
3 operators and #hiring uses 0. There is also a cap of 20 quoted phrases and 5
levels of nesting, both well above normal use.
What we normalise for you
These rewrites are applied silently, because each of them is something the underlying search would otherwise handle worse than you expect:| You send | We send |
|---|---|
Curly quotes “head of growth” | Straight quotes "head of growth" |
Lowercase operators saas or fintech | saas OR fintech |
A bare & between terms (saas & pricing) | saas AND pricing |
Single-quote delimiters 'head of growth' | A real phrase "head of growth" |
| Repeated or exotic whitespace | Single spaces |
we'll be raising soon) is left alone — only a matched pair of single
quotes at word boundaries becomes a phrase.
Every rewrite is auditable: the response echoes the query it actually ran as
query. Compare it to what you sent whenever a result set surprises you.
Pagination
Send the first page withoutcursor, then pass the cursor from each response
back into the next request, unchanged, with the same query and filters.
Stop when cursor is null.
async function searchAllPosts(body, apiKey, maxPages = 10) {
const seen = new Set();
const out = [];
let cursor = null;
for (let page = 0; page < maxPages; page++) {
const response = await fetch('https://api.fetchin.io/api/v1/posts/search', {
method: 'POST',
headers: { 'X-API-Key': apiKey, 'Content-Type': 'application/json' },
body: JSON.stringify({ ...body, maxResults: 50, cursor }),
});
if (!response.ok) throw new Error(`${response.status}: ${(await response.json()).error}`);
const data = await response.json(); // this page cost 1 credit
for (const post of data.results) {
if (seen.has(post.id)) continue; // posts can repeat across pages
seen.add(post.id);
out.push(post);
}
cursor = data.cursor;
if (cursor === null) break; // result set exhausted
}
return out;
}
import requests
def search_all_posts(body, api_key, max_pages=10):
seen, out, cursor = set(), [], None
for _ in range(max_pages):
resp = requests.post(
'https://api.fetchin.io/api/v1/posts/search',
headers={'X-API-Key': api_key},
json={**body, 'maxResults': 50, 'cursor': cursor},
)
resp.raise_for_status()
data = resp.json() # this page cost 1 credit
for post in data['results']:
if post['id'] in seen: # posts can repeat across pages
continue
seen.add(post['id'])
out.append(post)
cursor = data['cursor']
if cursor is None: # result set exhausted
break
return out
cursoris opaque. Pass it back byte for byte; do not parse or build one. A cursor issued for a different query or different filters is a400('cursor' belongs to a different search.), and so is anything this API did not issue.maxResultsmay change between pages, the query and filters may not.cursorisnullin every way a walk ends — a last page with results, an empty page past the end, and a query that matched nothing — so one condition closes your loop.- De-duplicate on
id. The result set is live and re-orders between calls; across a measured 4-page walk, 40 results held 37 distinct posts.
cursor comes
back null while posts matching your words certainly still exist. To go
further, split the query — by date window (postedWithin, postedAfter) or by
author — and combine the pages.Response
null on an author-only search.relevance, date, or null when sortBy was
omitted and the underlying search used its own.null when the result set is exhausted.droppedResults and a
reasons breakdown. Diagnostic and additive — log it, do not branch on the
keys of reasons.maxResults, and can be empty.Show Post object
Show Post object
urn:li:activity:...). Stable; de-duplicate on it.id, for convenience.urn:li:share:... / urn:li:ugcPost:... namespace,
when the card carries it.isEdited.3d. Informational;
compute ages from date.true when the post was edited after publication. The text returned is the
current one.postType to tell those apart.text, image,
video, document, article, poll, job, newsletter, celebration,
or other for an attachment kind introduced after this was written.
article means a link preview card is attached.type plus whatever that type provides — url,
title, subtitle, thumbnailUrl, width, height, aspectRatio,
pageCount and carousel for a document, options for a poll.url, width, height), largest
rendition each. Omitted when the post has none.title, subtitle, link,
image.type to tell them apart. A field the card did not carry is omitted
rather than guessed.Show Author object
Show Author object
profile when a person published the post, company when an
organization page did.profile, the numeric page id on a company. The
profile id is the same value as the profileId returned by
Fetch Profile, so posts can be
joined to profiles.id.headline when you
want the headline only./in/ prefix. Only on type: "profile".type: "company".url, width, height), largest rendition the card
offered. Read the size from the fields, not from the URL.websiteLabel.true when the card carries a premium badge. Omitted otherwise — an
absent badge is not proof the account is not premium.LIKE, PRAISE, EMPATHY, INTEREST, APPRECIATION, ENTERTAINMENT,
SUPER_LIKE — as type / count pairs. Use this one when you need every
type.like, appreciation, empathy, interest, praise. It has
no slot for entertainment or superLike, which is exactly why
reactionTypeCounts exists alongside it.type (person or company), name and
url. Empty when there are none.#. Empty when there are none.true when this post quotes another one.isRepost is true: id, shareUrn, date, the
original’s author (same shape as above) and its content, plus its
mentions, hashtags and outboundLinks. So a repost is usable without a
second call — but engagement counts are not carried here; fetch the quoted
post by its id if you need them.false.Example Request
curl -X POST "https://api.fetchin.io/api/v1/posts/search" \
-H "X-API-Key: your-api-key-here" \
-H "Content-Type: application/json" \
-d '{
"query": "\"head of growth\" AND (saas OR fintech)",
"postedWithin": "week",
"maxResults": 25
}'
const response = await fetch('https://api.fetchin.io/api/v1/posts/search', {
method: 'POST',
headers: {
'X-API-Key': 'your-api-key-here',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: '"head of growth" AND (saas OR fintech)',
postedWithin: 'week',
maxResults: 25,
}),
});
const page = await response.json();
console.log(page.query, page.sort, page.results.length);
import requests
response = requests.post(
'https://api.fetchin.io/api/v1/posts/search',
headers={'X-API-Key': 'your-api-key-here'},
json={
'query': '"head of growth" AND (saas OR fintech)',
'postedWithin': 'week',
'maxResults': 25,
},
)
page = response.json()
print(page['query'], page['sort'], len(page['results']))
<?php
$ch = curl_init('https://api.fetchin.io/api/v1/posts/search');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'X-API-Key: your-api-key-here',
'Content-Type: application/json',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
'query' => '"head of growth" AND (saas OR fintech)',
'postedWithin' => 'week',
'maxResults' => 25,
]));
$page = json_decode(curl_exec($ch), true);
curl_close($ch);
print_r($page);
?>
{
"authorCompanies": ["1035"],
"postedWithin": "month",
"maxResults": 50
}
Example Response
{
"query": "\"head of growth\" AND (saas OR fintech)",
"sort": null,
"results": [
{
"id": "urn:li:activity:7486820978292072449",
"entityId": "7486820978292072449",
"shareUrn": "urn:li:ugcPost:7486820977411145728",
"shareUrl": "https://www.linkedin.com/feed/update/urn:li:activity:7486820978292072449/",
"date": "2026-10-04T16:33:39.632Z",
"postedAgoShort": "3d",
"isEdited": false,
"content": "We just hired our first Head of Growth. Three things I wish I had known before writing the job description for a SaaS company this size...",
"postType": "image",
"media": [
{
"type": "image",
"url": "https://media.licdn.com/dms/image/v2/D4E22AQ.../feedshare-shrink_2048_1536/0/1759590819000?e=1762992000&v=beta&t=...",
"width": 2048,
"height": 1365,
"aspectRatio": 1.5
}
],
"images": [
{
"url": "https://media.licdn.com/dms/image/v2/D4E22AQ.../feedshare-shrink_2048_1536/0/1759590819000?e=1762992000&v=beta&t=...",
"width": 2048,
"height": 1365
}
],
"imageUrl": "https://media.licdn.com/dms/image/v2/D4E22AQ.../feedshare-shrink_2048_1536/0/1759590819000?e=1762992000&v=beta&t=...",
"author": {
"type": "profile",
"id": "ACoAAA8BYqEBCGLg_vT_ca6mMEqkpp9nVffJ3hc",
"name": "Marie Dupont",
"url": "https://www.linkedin.com/in/marie-dupont",
"publicIdentifier": "marie-dupont",
"info": "Co-founder & CEO at Northbound",
"headline": "Co-founder & CEO at Northbound",
"avatar": {
"url": "https://media.licdn.com/dms/image/v2/D4E03AQ.../profile-displayphoto-shrink_400_400/0/1726000000000?e=1762992000&v=beta&t=...",
"width": 400,
"height": 400
}
},
"reactionCount": 412,
"commentCount": 37,
"sharesCount": 8,
"reactionTypeCounts": [
{ "type": "LIKE", "count": 351 },
{ "type": "PRAISE", "count": 33 },
{ "type": "EMPATHY", "count": 12 },
{ "type": "INTEREST", "count": 9 },
{ "type": "APPRECIATION", "count": 7 },
{ "type": "ENTERTAINMENT", "count": 0 },
{ "type": "SUPER_LIKE", "count": 0 }
],
"reactionsByType": {
"like": 351,
"appreciation": 7,
"empathy": 12,
"interest": 9,
"praise": 33
},
"mentions": [
{
"type": "company",
"name": "Northbound",
"url": "https://www.linkedin.com/company/northbound"
}
],
"hashtags": ["hiring", "saas"],
"outboundLinks": ["https://northbound.example.com/careers"],
"isRepost": false,
"commentingDisabled": false
},
{
"id": "urn:li:activity:7489894675928027136",
"entityId": "7489894675928027136",
"shareUrn": "urn:li:share:7489894675324166145",
"shareUrl": "https://www.linkedin.com/feed/update/urn:li:activity:7489894675928027136/",
"date": "2026-10-06T04:07:26.255Z",
"postedAgoShort": "1d",
"isEdited": true,
"content": "This matches what we see on the fintech side too.",
"postType": "text",
"media": [],
"author": {
"type": "company",
"id": "1035",
"name": "Northbound",
"url": "https://www.linkedin.com/company/northbound",
"universalName": "northbound",
"info": "12,431 followers"
},
"reactionCount": 64,
"commentCount": 3,
"sharesCount": 1,
"reactionTypeCounts": [
{ "type": "LIKE", "count": 58 },
{ "type": "PRAISE", "count": 4 },
{ "type": "EMPATHY", "count": 1 },
{ "type": "INTEREST", "count": 1 },
{ "type": "APPRECIATION", "count": 0 },
{ "type": "ENTERTAINMENT", "count": 0 },
{ "type": "SUPER_LIKE", "count": 0 }
],
"reactionsByType": {
"like": 58,
"appreciation": 0,
"empathy": 1,
"interest": 1,
"praise": 4
},
"mentions": [],
"hashtags": [],
"outboundLinks": [],
"isRepost": true,
"resharedPost": {
"id": "urn:li:activity:7486820978292072449",
"shareUrn": "urn:li:ugcPost:7486820977411145728",
"date": "2026-10-04T16:33:39.632Z",
"author": {
"type": "profile",
"id": "ACoAAA8BYqEBCGLg_vT_ca6mMEqkpp9nVffJ3hc",
"name": "Marie Dupont",
"url": "https://www.linkedin.com/in/marie-dupont",
"publicIdentifier": "marie-dupont",
"headline": "Co-founder & CEO at Northbound"
},
"content": "We just hired our first Head of Growth. Three things I wish I had known before writing the job description for a SaaS company this size...",
"hashtags": ["hiring", "saas"]
}
}
],
"cursor": "eyJ2IjoxLCJxIjoiOWYyYzFlN2E0YjhkMzA1NiIsImYiOiJjMWE0ZTkwYjdkMzI2ODU0IiwiaSI6NTAsIm4iOjUwLCJjIjowfQ"
}
{
"query": "\"quantum pastry logistics\"",
"sort": null,
"results": [],
"cursor": null
}
{
"error": "'query' uses too many boolean operators: 6 (maximum 5). Split it into several searches and combine the results.",
"code": "INVALID_PARAMETER",
"details": {
"operators": 6,
"maxOperators": 5
}
}
{
"error": "'contentType[0]' must be one of: image, video, liveVideo, jobPost, document (received 'pdf').",
"code": "INVALID_PARAMETER"
}
{
"error": "Invalid API key",
"code": "INVALID_API_KEY"
}
{
"error": "Quota exceeded. Upgrade your plan or wait for renewal.",
"code": "QUOTA_EXHAUSTED",
"details": {
"creditsRemaining": 0,
"subscriptionCreditsRemaining": 0,
"paygCreditsRemaining": 0,
"buyCreditsUrl": "https://fetchin.io/credits"
}
}
{
"error": "Service temporarily unable to serve this request. Please retry.",
"code": "SERVICE_UNAVAILABLE"
}
Errors
See Error Handling for the shared envelope and recommended handling. This endpoint has no404: a search that matches nothing
is a 200 with an empty results.
| HTTP | code | When | Credits |
|---|---|---|---|
400 | INVALID_PARAMETER | Any body that breaks a rule above: unknown key, unrecognised enum value, maxResults over 50, more than 5 boolean operators, unbalanced parentheses or a dangling operator, a query with nothing to search for, a hashtag term together with sortBy: "date", no query and no author filter, a malformed or foreign cursor, malformed JSON | 0 |
401 | UNAUTHENTICATED / INVALID_API_KEY | Missing or invalid X-API-Key | 0 |
402 | QUOTA_EXHAUSTED | You have no credit left for this call | 0 |
429 | RATE_LIMITED | Your per-second rate limit, or more searches running at the same moment than your plan’s concurrency allowance (Too many searches running at once for your account. This endpoint holds each search open for several seconds, so a burst can exceed your plan even when your request rate does not. Wait for one to finish and retry., Retry-After: 1) | 0 |
500 | INTERNAL_ERROR | An unexpected bug on our side | 0 |
503 | SERVICE_UNAVAILABLE | Search capacity is saturated, or the page could not be read and we will not hand you a page we do not trust. Retry-After: 5 | 0 |
429 from this cap means you have far more searches open at once than your
rate implies, not that your plan is too small. Lower your concurrency and
honour Retry-After.503 here is never a partial page. If a page cannot be read in full we
refuse it and charge nothing, rather than return a short page you would have to
distrust. Retry it.Performance
Observed, not guaranteed: roughly 1–2 s for a small page (up to a handful of results) and 4.5–7 s for a full 50-result page, measured across local and production runs on 2026-10-07. Treat that as a range to size a timeout against rather than a service level — plan a per-request timeout of at least 30 seconds. Because a page is held open for seconds, parallel searches add up fast: see the concurrency note above.Limitations
Stated plainly, because you will hit every one of them in an afternoon of real use.Engagement counts are a snapshot
Engagement counts are a snapshot
reactionCount, commentCount, sharesCount, reactionTypeCounts and
reactionsByType are read at the moment of the fetch. A post found an hour
ago has moved since. Re-read the post when the number matters, and do not
treat two pages of the same walk as counted at the same instant.The date is the publication date, even on an edited post
The date is the publication date, even on an edited post
isEdited: true is the only signal that the two differ. There is
no edit timestamp.Media URLs expire
Media URLs expire
width / height, not from the path.Results can repeat across pages
Results can repeat across pages
id.A hashtag query cannot be sorted by date
A hashtag query cannot be sorted by date
#hiring on its own works. #hiring with sortBy: "date" returns nothing
at all upstream, so the combination is refused with 400 instead of billing
you for an empty page. Drop the #, or drop the sort.Sorting by date on a tight window narrows hard
Sorting by date on a tight window narrows hard
sortBy: "date" with postedWithin: "24h" returns markedly fewer results
than the same query unsorted — measured 1 against 6 on "hiring". When you
want coverage of a narrow window, omit sortBy and order the results
yourself on date.Depth is a few hundred results per query
Depth is a few hundred results per query
cursor goes null well before you have everything that matches your words.
Split by date window or by author, as in Pagination.Keyword matching is not ours to tune
Keyword matching is not ours to tune
query when a result
set surprises you.Notes
maxResults: 50. The call costs 1 credit at any page size, so a
smaller page is strictly worse value.Authorizations
Body
Send query, or at least one of authorProfiles / authorCompanies. Every other field is optional. null reads as "field omitted" everywhere. The body is validated strictly: an unknown key, an unrecognised enum value or a list that is too long is rejected with 400 and costs nothing, rather than being accepted and ignored.
The search terms. Supports quoted phrases, AND / OR / NOT, parentheses with nesting up to 5 levels, and hashtag terms. At most 5 boolean operators - this is a limit of the underlying search, and a 6th is refused with 400 rather than silently matching nothing, so split the query into several searches and combine the results. At most 20 quoted phrases. Curly quotes, lowercase operators, single-quote delimiters and a bare & are normalised silently, and the interpreted query is echoed back as the response's query. Required unless authorProfiles or authorCompanies is set.
500"\"head of growth\" AND (saas OR fintech) NOT intern"
How to order the results. No default: omit it and the underlying search applies its own ordering, which the response reports as sort: null. date is newest first. date cannot be combined with a hashtag term in query (400), and on a tight postedWithin window it returns markedly fewer results than no sort at all.
relevance, date Keep only posts published within this window. Applied by the search itself, so it also changes which results exist.
24h, week, month Keep only posts published at or after this instant, as an ISO-8601 timestamp or an epoch in milliseconds. Must not be in the future and at most 365 days ago. Applied by us after the search runs, so a page filtered this way comes back short and still costs its 1 credit. Combine it with postedWithin to narrow inside a window.
"2026-10-01T00:00:00Z"
Keep only posts carrying one of these kinds of attachment. Values OR together.
1 - 5 elementsimage, video, liveVideo, jobPost, document ["document"]
Keep only posts published by these people. 1 to 10 values, each a profile id or any profile URL containing one. A slug on its own is not accepted here - resolve it with GET /api/v1/profile first. Can be sent without query to page a set of authors.
1 - 10 elements["ACoAAA8BYqEBCGLg_vT_ca6mMEqkpp9nVffJ3hc"]
Keep only posts published by these organization pages. 1 to 10 values, each a numeric page id, a page urn, or a page URL ending in a numeric id. Can be sent without query.
1 - 10 elements["1035"]
Keep only posts that mention these organization pages. 1 to 10 numeric page ids, urns or URLs.
1 - 10 elements["1035"]
Keep only posts whose author's current title matches this free text. Matched as a phrase.
1 - 100"Head of Growth"
How many results this page may return. 50 is both the default and the hard ceiling; a larger value is refused with 400 because the underlying search answers nothing at all above it. A page can return fewer results than this: results are de-duplicated by post id within the page (a 50-result request measured 49), and the last page of a walk is short by nature.
1 <= x <= 50Opaque cursor for the next page. Pass back the cursor from the previous response, byte for byte, with the same query and filters; omit it to start a new search. A cursor issued for a different query, or any value this API did not issue, is refused with 400. Keep paging while cursor is not null.
1024Response
A page of matching posts. Billed 1 credit, including when results is empty. Observed latency, not a guarantee: ~1-2 s for a small page and 4.5-7 s for a full 50-result page.
The query as it was interpreted, after normalisation - so every silent rewrite is auditable. null on an author-only search.
The ordering that was applied, echoed from the request. null when sortBy was omitted, meaning the underlying search used its own ordering, which we do not name.
relevance, date, null Matching posts, de-duplicated within the page. Can hold fewer items than maxResults, and can be empty - which still costs 1 credit.
Show child attributes
Show child attributes
Opaque cursor for the next page, or null when this result set is exhausted. Pass it back unchanged with the same query and filters. One query is exhausted after a few hundred results.
Present only when at least one card on the page could not be read and was dropped, so a short page is never silently short. Diagnostic and additive: log it, do not branch on the keys of reasons.
Show child attributes
Show child attributes