Bluesky Post Search Scraper
Pricing
from $0.70 / 1,000 bluesky post scrapeds
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
Maintained by CommunityActor stats
0
Bookmarked
10
Total users
7
Monthly active users
4 days ago
Last modified
Categories
Features
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.
| Field | Type | Purpose |
|---|---|---|
searchQuery | string | Focused search text or supported Lucene-style query |
searchQueries | array[string] | More queries to run in the same run (up to 20), each with the same filters |
sort | string | latest or top ordering from Bluesky search |
maxResults | integer | Maximum posts per query, from 1 to 1000 (latest order; top order stops at 100) |
since | string | Lower bound for indexed search time |
until | string | Exclusive upper bound for indexed search time |
lang | string | BCP 47 language tag |
author | string | Public Bluesky author handle or DID |
mentions | string | Mentioned account handle or DID |
domain | string | Linked domain hostname |
url | string | Absolute linked URL |
tags | array[string] | Hashtag facets, up to 10 tags |
includeReplies | boolean | Whether reply posts may appear in results |
minLikes | integer | Keep only posts with at least this many likes (skipped posts are not charged) |
minReposts | integer | Keep only posts with at least this many reposts |
minReplies | integer | Keep only posts with at least this many replies |
onlyNewSinceLastRun | boolean | Return only posts newer than the last post this search delivered (needs latest order) |
monitoringStoreName | string | Optional 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.
| Field | Type | Purpose |
|---|---|---|
searchQuery | string | Exact query used for the row |
sort | string | Ranking mode used for the search |
rank | integer | One-based rank in the run |
postUri | string | Stable AT Protocol URI |
postCid | string | CID of the returned post version |
postUrl | string | Human-readable Bluesky post URL |
text | string | Public post text |
authorDid | string | Author DID |
authorHandle | string | Author handle |
authorDisplayName | string or null | Public display name when available |
authorAvatarUrl | string or null | Public avatar URL when returned |
createdAt | string | Post record timestamp |
indexedAt | string | AppView index timestamp |
langs | array[string] | Declared language tags |
likeCount | integer | Likes at collection time |
repostCount | integer | Reposts at collection time |
replyCount | integer | Replies at collection time |
quoteCount | integer | Quote posts at collection time |
engagementCount | integer | Sum of engagement counts |
isReply | boolean | Reply indicator |
replyRootUri | string or null | Reply root AT URI |
replyParentUri | string or null | Direct parent AT URI |
links | array[string] | Rich-text link URLs |
mentionedDids | array[string] | Mention facet DIDs |
tags | array[string] | Hashtags found on the post |
labels | array[string] | Content labels returned on the post view |
imageUrls | array[string] | Public image URLs |
imageAltTexts | array[string] | Image alt text aligned by index |
videoPlaylistUrl | string or null | HLS playlist URL for video embeds |
videoThumbnailUrl | string or null | Video thumbnail URL |
externalUrl | string or null | External card URL |
externalTitle | string or null | External card title |
externalDescription | string or null | External card description |
externalThumbUrl | string or null | External card thumbnail |
quotedPostUri | string or null | Quoted post AT URI |
collectedAt | string | Dataset normalization timestamp |
sourceEndpoint | string | Bluesky AppView endpoint used |
_note | string or null | Agent-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
rankshows the order of persisted rows in the runsourceEndpointidentifies the Bluesky-hosted AppView routecollectedAtrecords when the row was normalized_notemay 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:
maxResultscan be set from 1 to 1000 per query inlatestorder;toporder stops at 100- for a recurring agent job, turn on
onlyNewSinceLastRunso 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
- Then use Bluesky Posts Scraper — Search, Feeds & Threads to continue from Bluesky Post Search Scraper — Posts & Metrics discovery into content data for the selected records.
- Then use Skool Community Scraper to extend Bluesky Post Search Scraper — Posts & Metrics with a neighboring social-media research source when the brief calls for Skool data.
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.