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.

GET/api/v1/feeds/searchSearch public posts by keyword
GET/api/v1/feeds/keywordsList saved keywords
POST/api/v1/feeds/keywordsSave a keyword
DELETE/api/v1/feeds/keywordsDelete a saved keyword
GET/api/v1/feeds/profileLook up a public profile and its recent posts

Threads 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.

GET/api/v1/feeds/searchSearch public posts by keyword

Keyword 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

platform
stringRequired

The platform to search.

blueskythreads
q
stringRequired

The search query. Threads requires at least 2 characters.

accountId
stringOptional

Which connected account to search through. Defaults to the first connected account for that platform.

limit
integerOptional

Number of posts to return. Capped at 100.

Default: 25

cursor
stringOptional

Bluesky only. Pagination cursor from a previous response.

sort
stringOptional

Bluesky only. Sort by most recent (latest) or most engaged (top).

Default: latest

latesttop
# Search Bluesky, most recent first
curl "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: search
const 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 carries
await 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, rootCid
message: '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

GET/api/v1/feeds/keywordsList saved keywords

Query parameters

platform
stringRequired

Which platform's keyword list to return.

blueskythreads
curl "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

POST/api/v1/feeds/keywordsSave a keyword

Request body

platform
stringRequired

Which platform's keyword list to add to.

blueskythreads
keyword
stringRequired

The 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

DELETE/api/v1/feeds/keywordsDelete a saved keyword

Query parameters

platform
stringRequired

Which platform's keyword list to remove from.

blueskythreads
keyword
stringRequired

The 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

GET/api/v1/feeds/profileLook up a public profile and its recent posts

Look 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

platform
stringOptional

The platform. Only bluesky is supported.

Default: bluesky

bluesky
handle
stringRequired

The handle to look up (for example yourbrand.bsky.social). A leading @ is stripped automatically.

accountId
stringOptional

Which connected account to look up through. Defaults to the first connected Bluesky account.

limit
integerOptional

Number 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

StatusCodeWhen it happens
400validation_errorMissing or unsupported platform, missing q / handle / keyword, no connected account for the platform, or the Threads search budget is exhausted.
400sandbox_unsupportedThreads search called with a test (sk_test_) key.
403platform_permission_errorThe platform denied the request. The account may need to be reconnected.
409validation_errorSaving a keyword that already exists on the list.
502platform_errorThe platform's API returned an unexpected error.

Related