Bluesky Post Search Scraper avatar

Bluesky Post Search Scraper

Pricing

from $0.70 / 1,000 bluesky post scrapeds

Go to Apify Store
Bluesky Post Search Scraper

Bluesky Post Search Scraper

Search public Bluesky posts by keyword, with author, hashtag, mention, domain, language, date and minimum-likes filters. Newest-first searches return up to 1,000 posts per query, with an only-new mode for scheduled monitoring. No Bluesky account needed.

Pricing

from $0.70 / 1,000 bluesky post scrapeds

Rating

0.0

(0)

Developer

Khadin Akbar

Khadin Akbar

Maintained by Community

Actor stats

0

Bookmarked

10

Total users

7

Monthly active users

4 days ago

Last modified

Features

Limited permissions

Share

What is Bluesky Post Search Scraper?

Bluesky Post Search Scraper is an Apify Actor for searching public Bluesky posts by keyword, author, date, language, hashtag, mention, linked domain, linked URL, or minimum likes. It runs one query or a list of up to 20 queries per run (searchQueries), needs no Bluesky account, and returns one structured record per post with text, author identity, timestamps, engagement counts, media fields, facets, and source URLs. Newest-first searches return up to 1,000 posts per query, and an only-new mode returns just the posts that appeared since your last run, which suits scheduled monitoring and AI-agent workflows.

Best fit and connected workflows

This Actor fits workflows that start with a targeted Bluesky search and end with a structured list of matching public posts. Typical routing includes:

  • topic monitoring from a phrase or Lucene-style query
  • author-scoped searches for public accounts
  • language, hashtag, mention, domain, and linked-URL filtering
  • keeping only posts with a minimum number of likes, reposts, or replies
  • scheduled monitoring that returns only posts new since the previous run
  • Use follow-up review of selected posts with Bluesky Posts Scraper - Search, Feeds & Threads when you need a downstream workflow that operates on a returned public URL or ID

Because each dataset row represents one post, the output is easy to filter, store, enrich, or hand to an agent for later steps.

Practical scenario

A product marketer tracks launch chatter for her brand and two competitors. She runs searchQuery: "notion" with searchQueries: ["obsidian app", "logseq"], sets lang to en, and keeps maxResults at 25. The Actor returns rows with postUrl, text, authorHandle, createdAt, likeCount, repostCount, replyCount, quoteCount, tags, and externalUrl. She spots one post that links to a release note page and has strong engagement. Her next action is to open the post URL for review and pass the public URL into a downstream enrichment workflow.

Input

The Actor needs searchQuery (or a searchQueries list), plus optional server-side filters.

FieldTypePurpose
searchQuerystringFocused search text or supported Lucene-style query
searchQueriesarray[string]More queries to run in the same run (up to 20), each with the same filters
sortstringlatest or top ordering from Bluesky search
maxResultsintegerMaximum posts per query, from 1 to 1000 (latest order; top order stops at 100)
sincestringLower bound for indexed search time
untilstringExclusive upper bound for indexed search time
langstringBCP 47 language tag
authorstringPublic Bluesky author handle or DID
mentionsstringMentioned account handle or DID
domainstringLinked domain hostname
urlstringAbsolute linked URL
tagsarray[string]Hashtag facets, up to 10 tags
includeRepliesbooleanWhether reply posts may appear in results
minLikesintegerKeep only posts with at least this many likes (skipped posts are not charged)
minRepostsintegerKeep only posts with at least this many reposts
minRepliesintegerKeep only posts with at least this many replies
onlyNewSinceLastRunbooleanReturn only posts newer than the last post this search delivered (needs latest order)
monitoringStoreNamestringOptional name for the memory used by only-new mode (default bluesky-post-search-monitor)

Focused input example:

{
"searchQuery": "open source",
"sort": "latest",
"maxResults": 25,
"since": "2026-07-01",
"until": "2026-07-15",
"lang": "en",
"author": "jay.bsky.team",
"mentions": "",
"domain": "github.com",
"url": "",
"tags": ["opensource"],
"includeReplies": true
}

Output

Each dataset row contains one validated public Bluesky post.

FieldTypePurpose
searchQuerystringExact query used for the row
sortstringRanking mode used for the search
rankintegerOne-based rank in the run
postUristringStable AT Protocol URI
postCidstringCID of the returned post version
postUrlstringHuman-readable Bluesky post URL
textstringPublic post text
authorDidstringAuthor DID
authorHandlestringAuthor handle
authorDisplayNamestring or nullPublic display name when available
authorAvatarUrlstring or nullPublic avatar URL when returned
createdAtstringPost record timestamp
indexedAtstringAppView index timestamp
langsarray[string]Declared language tags
likeCountintegerLikes at collection time
repostCountintegerReposts at collection time
replyCountintegerReplies at collection time
quoteCountintegerQuote posts at collection time
engagementCountintegerSum of engagement counts
isReplybooleanReply indicator
replyRootUristring or nullReply root AT URI
replyParentUristring or nullDirect parent AT URI
linksarray[string]Rich-text link URLs
mentionedDidsarray[string]Mention facet DIDs
tagsarray[string]Hashtags found on the post
labelsarray[string]Content labels returned on the post view
imageUrlsarray[string]Public image URLs
imageAltTextsarray[string]Image alt text aligned by index
videoPlaylistUrlstring or nullHLS playlist URL for video embeds
videoThumbnailUrlstring or nullVideo thumbnail URL
externalUrlstring or nullExternal card URL
externalTitlestring or nullExternal card title
externalDescriptionstring or nullExternal card description
externalThumbUrlstring or nullExternal card thumbnail
quotedPostUristring or nullQuoted post AT URI
collectedAtstringDataset normalization timestamp
sourceEndpointstringBluesky AppView endpoint used
_notestring or nullAgent-readable continuation note

Illustrative output record:

{
"searchQuery": "open source",
"sort": "latest",
"rank": 1,
"postUri": "at://did:plc:example/app.bsky.feed.post/3abc",
"postCid": "bafyreiexample",
"postUrl": "https://bsky.app/profile/alice.bsky.social/post/3abc",
"text": "A new open-source release is available today.",
"authorDid": "did:plc:example",
"authorHandle": "alice.bsky.social",
"authorDisplayName": "Alice",
"authorAvatarUrl": null,
"createdAt": "2026-07-15T10:00:00.000Z",
"indexedAt": "2026-07-15T10:00:01.000Z",
"langs": ["en"],
"likeCount": 42,
"repostCount": 7,
"replyCount": 5,
"quoteCount": 2,
"engagementCount": 56,
"isReply": false,
"replyRootUri": null,
"replyParentUri": null,
"links": ["https://example.com/release"],
"mentionedDids": [],
"tags": ["opensource"],
"labels": [],
"imageUrls": [],
"imageAltTexts": [],
"videoPlaylistUrl": null,
"videoThumbnailUrl": null,
"externalUrl": "https://example.com/release",
"externalTitle": "Release notes",
"externalDescription": "What's new in this version.",
"externalThumbUrl": null,
"quotedPostUri": null,
"collectedAt": "2026-07-15T11:00:00.000Z",
"sourceEndpoint": "https://api.bsky.app/xrpc/app.bsky.feed.searchPosts",
"_note": null
}

The Actor also writes compact OUTPUT and diagnostic RUN_SUMMARY records to the key-value store.

How it works

This Actor searches public Bluesky posts through Bluesky's public AppView API. Each query runs with the same optional server-side filters; posts found by more than one query are saved once. The implementation uses the public app.bsky.feed.searchPosts route and normalizes the returned post view into dataset rows. The dataset schema keeps all listed fields explicit, with null or empty arrays where applicable, so downstream tools can read the data consistently.

Going past 100 posts. One Bluesky search page holds at most 100 posts, and the public route does not allow page-by-page continuation. In latest order the Actor therefore moves a time window backwards: after each page it searches again for posts older than the oldest one it has, removes the repeated boundary post, and continues until it reaches maxResults, runs out of matches, or has scanned 3,000 posts for that query. top order has no time direction, so it stays at one page of up to 100 posts. If Bluesky changes this behavior, the run still returns what it collected and says so in warnings.

Only new posts. With onlyNewSinceLastRun, each query and filter combination keeps a small record of the newest post it delivered, in a named storage of your account. The next run asks only for newer posts. If more new posts exist than maxResults, the oldest of them are not delivered later, so raise maxResults or schedule runs more often. Changing the query or any filter starts a fresh memory.

Time limit. A run stops a little before its time limit and finishes as PARTIAL with the posts already saved, rather than timing out.

When direct Bluesky search is access-restricted, the Actor retries through a managed unblocker route (at most six pages per run), using the same query and filters; this fallback is covered because Apify platform usage is included. OUTPUT.unblocker and RUN_SUMMARY.unblocker report whether that fallback was used. If every route stays unavailable, the run reports UPSTREAM_FAILED without charging for posts.

Pricing

This Actor uses Pay per event with Apify platform usage included.

  • Actor start: charged once per run, scaled by the run's memory allocation
  • post-scraped: charged for each schema-valid post persisted to the default dataset (posts found by several queries are charged once)
  • Apify platform usage: included in the event price

A run that persists one hundred posts charges one start event plus one hundred post-scraped events; Apify platform usage is included. With searchQueries, the cap is maxResults posts per query. Posts skipped by minLikes, minReposts, minReplies, includeReplies, or only-new mode are not charged. Higher Apify plans see lower per-post prices on the live Pricing tab, which shows the current billable values and totals.

Use with AI agents (MCP)

This Actor is usable through Apify MCP as a tool for public Bluesky post search. The exact Actor identity is khadinakbar/search-bluesky-posts.

The tool is suitable used with a focused query, optional filters, and a bounded maxResults so an agent can read the returned dataset and continue with a second step if needed.

Search public Bluesky posts for "open source" from the last month, keep results in English, and return up to 25 posts. Then summarize the most relevant post URLs and engagement counts.

Output interpretation:

  • dataset rows contain one post each
  • rank shows the order of persisted rows in the run
  • sourceEndpoint identifies the Bluesky-hosted AppView route
  • collectedAt records when the row was normalized
  • _note may help an agent continue after a capped execution Provenance and scope:
  • results come from public Bluesky AppView search
  • the Actor returns public posts, authors, timestamps, engagement, media, and URL fields
  • The search scope is post search, not profiles, followers, or private data

Pagination and cost guidance:

  • maxResults can be set from 1 to 1000 per query in latest order; top order stops at 100
  • for a recurring agent job, turn on onlyNewSinceLastRun so each run returns only unseen posts
  • tighter filters, including minimum likes, make follow-up runs more specific
  • each persisted post creates a post-scraped event charge; skipped posts do not

JavaScript example

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('khadinakbar/search-bluesky-posts').call({
searchQuery: 'open source',
sort: 'latest',
maxResults: 25,
lang: 'en'
});
const dataset = await client.dataset(run.defaultDatasetId).listItems();
console.log(dataset.items);

Best results and outcome guidance

Start with one focused query, add related phrases to searchQueries for monitoring several topics in one run, and then narrow by author, language, date, hashtag, mention, domain, or URL as needed. If you are building an agent workflow, keep the first run small, inspect text, authorHandle, createdAt, and postUrl, then decide whether a second run should target a more specific slice of the search space. Use includeReplies based on whether reply posts belong in the review set.

Continue the workflow

Builder's note

I built this for brand and topic monitoring. I found that the dataset contract keeps searchQuery, sort, rank, postUrl, text, authorHandle, createdAt, and engagement counts in every row, which makes each record self-describing for later analysis.

FAQ

How many posts can one query return?

Up to 1,000 per query in latest order (set maxResults), and up to 100 in top order. The Actor reaches past Bluesky's 100-post page by moving a time window backwards, so a very busy phrase may still hold more posts than one run collects. Narrow it with since / until or add more phrases to searchQueries.

How does only-new mode work for a daily schedule?

Turn on onlyNewSinceLastRun and schedule the same input. The first run returns up to maxResults posts; each later run returns only posts newer than the last post it delivered, and finishes as VALID_EMPTY when nothing new appeared. Use monitoringStoreName to keep separate memories, for example one per client.

Why does top order stop at 100?

Relevance order has no time direction, so it cannot be walked backwards in time. Use latest order with a date range when you need depth.

Are posts skipped by minimum likes charged?

No. Only posts saved to the dataset create a post-scraped charge. A strict filter on a broad phrase can scan many posts to find a few matches, and a run stops after scanning 3,000 posts per query.

When should I use author instead of searchQuery?

Use author when you want public posts from one Bluesky account and searchQuery when you are tracking a topic, phrase, or query syntax pattern.

When should I use mentions?

Use mentions when the post must include a structured mention facet for a specific account handle or DID.

When should I use domain or url?

Use domain when you want posts linking to a hostname, and url when you need the exact linked web address.

How do tags work?

tags targets structured hashtags. Multiple tags are matched together, and the tags are entered without the # prefix.

What does includeReplies change?

includeReplies controls whether reply posts may remain in the final dataset after search results are returned.

How should I choose between latest and top?

Use latest for newest indexed posts and top for relevance-ranked results.

How can I use this output in a downstream workflow?

Use the returned postUrl, postUri, or authorHandle as the starting point for a second Apify workflow or for agent-side enrichment.

Responsible use

This Actor searches public Bluesky posts only. Use the returned data in line with applicable law, Bluesky and AT Protocol terms, and your own privacy and research obligations. Keep collection proportional to the task, store data carefully, and apply special care when combining public social data with other sources.