Feeds API (Social Listening)
Search public posts by keyword, save the keywords you track, and look up public profiles. Every result carries the reply target it needs, so you can go from a keyword match to a reply in two API calls. Search is available on Bluesky and Threads.
Permission: All Feeds endpoints require the engagement permission on your API key, the same permission the Engagement API uses.
/api/v1/feeds/searchSearch public posts by keyword/api/v1/feeds/keywordsList saved keywords/api/v1/feeds/keywordsSave a keyword/api/v1/feeds/keywordsDelete a saved keyword/api/v1/feeds/profileLook up a public profile and its recent postsThreads search budget: Meta caps Threads keyword search at 500 searches per rolling 7 days per user. This budget is shared across the Schedulala dashboard, this API, and the Claude connector; every search from any of them spends from the same pool. Once the budget is exhausted, requests are blocked before the Meta call with a 400 validation_error that includes the reset time. Every successful Threads search response includes a usage object so you can track consumption. Because the budget is real shared quota, Threads search is disabled for test (sk_test_) keys and returns error code sandbox_unsupported; Bluesky search is a free public read and works in sandbox.
Search public posts
/api/v1/feeds/searchSearch public posts by keywordKeyword search across public posts. Supported on bluesky (with sort and cursor pagination) and threads (no pagination; consumes the shared Meta budget described above). A connected account for the platform is required, since the search runs through that account's credentials.
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
platform | string | Required | The platform to search.blueskythreads |
q | string | Required | The search query. Threads requires at least 2 characters. |
accountId | string | Optional | Which connected account to search through. Defaults to the first connected account for that platform. |
limit | integer | Optional | Number of posts to return. Capped at 100.Default: 25 |
cursor | string | Optional | Bluesky only. Pagination cursor from a previous response. |
sort | string | Optional | Bluesky only. Sort by most recent (latest) or most engaged (top).Default: latestlatesttop |
platformstringRequiredThe platform to search.
blueskythreadsqstringRequiredThe search query. Threads requires at least 2 characters.
accountIdstringOptionalWhich connected account to search through. Defaults to the first connected account for that platform.
limitintegerOptionalNumber of posts to return. Capped at 100.
Default: 25
cursorstringOptionalBluesky only. Pagination cursor from a previous response.
sortstringOptionalBluesky only. Sort by most recent (latest) or most engaged (top).
Default: latest
latesttop# Search Bluesky, most recent firstcurl "https://schedulala.com/api/v1/feeds/search?platform=bluesky&q=social%20media%20scheduling&sort=latest&limit=25" \-H "Authorization: Bearer sk_live_abc123..."# Search Threads (spends 1 of the 500 weekly searches)curl "https://schedulala.com/api/v1/feeds/search?platform=threads&q=social%20media%20scheduling" \-H "Authorization: Bearer sk_live_abc123..."
{"success": true,"platform": "bluesky","account": {"id": "did:plc:abc123xyz","username": "yourbrand.bsky.social"},"query": "social media scheduling","posts": [{"id": "at://did:plc:xyz789/app.bsky.feed.post/3kabc123","text": "Looking for a good social media scheduling tool, any recommendations?","author": {"username": "jane.bsky.social","displayName": "Jane Doe","profilePicture": "https://cdn.bsky.app/img/avatar/plain/did:plc:xyz789/abc@jpeg"},"createdAt": "2026-06-10T18:22:00.000Z","likeCount": 4,"post": null,"platform": "bluesky","platformData": {"cid": "bafyreib2rxk3rw6example","permalink": "https://bsky.app/profile/jane.bsky.social/post/3kabc123","replyCount": 2,"repostCount": 1,"quoteCount": 0,"mediaUrl": null,"mediaType": "TEXT_POST","images": null,"external": null,"video": null,"isReply": false,"parentPost": null,"replyRefs": {"parentUri": "at://did:plc:xyz789/app.bsky.feed.post/3kabc123","parentCid": "bafyreib2rxk3rw6example","rootUri": "at://did:plc:xyz789/app.bsky.feed.post/3kabc123","rootCid": "bafyreib2rxk3rw6example"}}}],"nextCursor": "25","hasMore": true}
The result shape
Each search result uses the same unified shape as the Engagement API comment objects: id, text, author, createdAt, likeCount, and platform, with all platform-specific extras preserved under platformData. On Bluesky that includes engagement counts, media embeds (images, external links, video), and the reply refs. On Threads the raw Graph API fields are carried through as-is (snake_case keys such as media_type and reply_count).
From search result to reply
Every search result carries the reply target it needs, so results chain directly into POST /api/v1/engagement/reply. Bluesky posts carry platformData.replyRefs with the four AT-Protocol refs (parentUri, parentCid, rootUri, rootCid); pass them straight through. Threads posts reply by their post id: send it as postId.
// Step 1: searchconst search = await fetch('https://schedulala.com/api/v1/feeds/search?platform=bluesky&q=' +encodeURIComponent('social media scheduling'),{ headers: { 'Authorization': `Bearer ${process.env.SCHEDULALA_API_KEY}` } }).then((r) => r.json());const post = search.posts[0];// Step 2: reply using the refs the result already carriesawait fetch('https://schedulala.com/api/v1/engagement/reply', {method: 'POST',headers: {'Authorization': `Bearer ${process.env.SCHEDULALA_API_KEY}`,'Content-Type': 'application/json',},body: JSON.stringify({platform: 'bluesky',...post.platformData.replyRefs, // parentUri, parentCid, rootUri, rootCidmessage: 'We built exactly this, happy to share more if useful!',}),});
Saved keywords
Save the keywords you track so any client can re-run the same searches. Keyword lists are stored per platform (bluesky and threads), hold a maximum of 20 keywords each, and dedupe case-insensitively. They are the same lists the Schedulala dashboard's Feeds page uses, so the dashboard and the API always stay in sync.
List keywords
/api/v1/feeds/keywordsList saved keywordsQuery parameters
| Name | Type | Required | Description |
|---|---|---|---|
platform | string | Required | Which platform's keyword list to return.blueskythreads |
platformstringRequiredWhich platform's keyword list to return.
blueskythreadscurl "https://schedulala.com/api/v1/feeds/keywords?platform=bluesky" \-H "Authorization: Bearer sk_live_abc123..."
{"success": true,"platform": "bluesky","keywords": [{"keyword": "social media scheduling","createdAt": "2026-06-01T12:00:00.000Z"},{"keyword": "content calendar","createdAt": "2026-06-04T09:30:00.000Z"}]}
Save a keyword
/api/v1/feeds/keywordsSave a keywordRequest body
| Name | Type | Required | Description |
|---|---|---|---|
platform | string | Required | Which platform's keyword list to add to.blueskythreads |
keyword | string | Required | The keyword to save. Must be 2-100 characters. Duplicates are detected case-insensitively. |
platformstringRequiredWhich platform's keyword list to add to.
blueskythreadskeywordstringRequiredThe keyword to save. Must be 2-100 characters. Duplicates are detected case-insensitively.
curl -X POST https://schedulala.com/api/v1/feeds/keywords \-H "Authorization: Bearer sk_live_abc123..." \-H "Content-Type: application/json" \-d '{ "platform": "bluesky", "keyword": "social media scheduling" }'
{"success": true,"platform": "bluesky","keywords": [{"keyword": "social media scheduling","createdAt": "2026-06-11T10:00:00.000Z"}]}
Delete a keyword
/api/v1/feeds/keywordsDelete a saved keywordQuery parameters
| Name | Type | Required | Description |
|---|---|---|---|
platform | string | Required | Which platform's keyword list to remove from.blueskythreads |
keyword | string | Required | The keyword to remove. Matched case-insensitively against the exact keyword. |
platformstringRequiredWhich platform's keyword list to remove from.
blueskythreadskeywordstringRequiredThe keyword to remove. Matched case-insensitively against the exact keyword.
curl -X DELETE "https://schedulala.com/api/v1/feeds/keywords?platform=bluesky&keyword=content%20calendar" \-H "Authorization: Bearer sk_live_abc123..."
{"success": true,"platform": "bluesky","keywords": [{"keyword": "social media scheduling","createdAt": "2026-06-01T12:00:00.000Z"}]}
Deletes are idempotent: removing a keyword that is not saved still returns 200 with the unchanged list. Every keywords response returns the full updated list, so you never need a follow-up read.
Profile lookup
/api/v1/feeds/profileLook up a public profile and its recent postsLook up any public profile and its recent posts. Currently supported on bluesky only. You need a connected Bluesky account (the lookup runs through its credentials), but the profile you look up can be anyone's. The returned posts use the same shape as search results, including platformData.replyRefs for replying.
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
platform | string | Optional | The platform. Only bluesky is supported.Default: blueskybluesky |
handle | string | Required | The handle to look up (for example yourbrand.bsky.social). A leading @ is stripped automatically. |
accountId | string | Optional | Which connected account to look up through. Defaults to the first connected Bluesky account. |
limit | integer | Optional | Number of recent posts to return. Capped at 100.Default: 25 |
platformstringOptionalThe platform. Only bluesky is supported.
Default: bluesky
blueskyhandlestringRequiredThe handle to look up (for example yourbrand.bsky.social). A leading @ is stripped automatically.
accountIdstringOptionalWhich connected account to look up through. Defaults to the first connected Bluesky account.
limitintegerOptionalNumber of recent posts to return. Capped at 100.
Default: 25
curl "https://schedulala.com/api/v1/feeds/profile?handle=jane.bsky.social&limit=10" \-H "Authorization: Bearer sk_live_abc123..."
{"success": true,"platform": "bluesky","profile": {"did": "did:plc:xyz789abc","handle": "jane.bsky.social","displayName": "Jane Doe","description": "Designer. Coffee first, pixels second.","avatar": "https://cdn.bsky.app/img/avatar/plain/did:plc:xyz789abc/abc@jpeg","followersCount": 1832,"followsCount": 420,"postsCount": 956},"posts": [{"id": "at://did:plc:xyz789abc/app.bsky.feed.post/3kaaa111","text": "New portfolio is live!","author": {"username": "jane.bsky.social","displayName": "Jane Doe","profilePicture": "https://cdn.bsky.app/img/avatar/plain/did:plc:xyz789abc/abc@jpeg"},"createdAt": "2026-06-09T08:15:00.000Z","likeCount": 31,"post": null,"platform": "bluesky","platformData": {"cid": "bafyreid4kjx2example","permalink": "https://bsky.app/profile/jane.bsky.social/post/3kaaa111","replyCount": 5,"repostCount": 2,"quoteCount": 0,"mediaUrl": "https://cdn.bsky.app/img/feed_thumbnail/plain/did:plc:xyz789abc/img@jpeg","mediaType": "IMAGE","images": [{"thumb": "https://cdn.bsky.app/img/feed_thumbnail/plain/did:plc:xyz789abc/img@jpeg","fullsize": "https://cdn.bsky.app/img/feed_fullsize/plain/did:plc:xyz789abc/img@jpeg","alt": "Portfolio homepage screenshot"}],"external": null,"video": null,"isReply": false,"parentPost": null,"replyRefs": {"parentUri": "at://did:plc:xyz789abc/app.bsky.feed.post/3kaaa111","parentCid": "bafyreid4kjx2example","rootUri": "at://did:plc:xyz789abc/app.bsky.feed.post/3kaaa111","rootCid": "bafyreid4kjx2example"}}}],"nextCursor": "2026-06-01T10:00:00.000Z","hasMore": true}
Errors
| Status | Code | When it happens |
|---|---|---|
| 400 | validation_error | Missing or unsupported platform, missing q / handle / keyword, no connected account for the platform, or the Threads search budget is exhausted. |
| 400 | sandbox_unsupported | Threads search called with a test (sk_test_) key. |
| 403 | platform_permission_error | The platform denied the request. The account may need to be reconnected. |
| 409 | validation_error | Saving a keyword that already exists on the list. |
| 502 | platform_error | The platform's API returned an unexpected error. |