Universal social search
One endpoint that fans out across 14 social platforms (up to 17 sources) in parallel, then ranks, clusters, and comment-enriches the merged results. JSON or SSE.
Send one query to GET /v1/search/everywhere and get back a ranked, clustered, comment-enriched set of results pulled in parallel from up to 17 sources across 14 platforms. An LLM plans the query, results are fused and reranked, near-duplicates are clustered, and every result carries the top comments from its source, so you see what real people said and not just titles.
Returns one JSON envelope by default, or a live SSE stream when you ask for one. Flat 20 credits per call.
curl "https://www.socialcrawl.dev/v1/search/everywhere?query=apple+vision+pro+reviews" \
-H "x-api-key: YOUR_API_KEY"This is the only endpoint that bypasses the per-source 1/5/10 tier ladder. The 20 credits are flat, however many sources fire and however many comments are enriched.
How the fan-out works
The pipeline runs in five stages, and every one of them is visible in the response.
- Plan. An LLM decomposes your query into weighted subqueries and picks which sources each subquery targets. A fast planner scores per-source relevance at the same time, and a source it judges irrelevant for the query is pruned, so its results are not ranked and it leaves the coverage denominator. The planner fires an abort on a pruned source, but a source that had already answered before the abort landed still reports its
items, so treat a prune as "this source was not ranked" rather than as proof that no request was made. - Fan out. Every surviving source runs in parallel, each bounded by a 12-second per-source timeout.
twitter-ai-searchruns as a background source with a longer budget, because Grok synthesis is slow. Its result still lands before the run finishes. - Fuse and dedupe. Results are merged, and the same item found by two sources becomes one candidate with multi-source provenance and a higher fused score.
- Enrich and rerank. Top comments are fetched in parallel with an LLM rerank pass that runs on a 15-second budget. Partial scores are kept on timeout.
- Cluster. Near-duplicates and same-story groupings are collapsed into
clusters, when the resolved intent is clusterable.
Sources
The default fan-out hits every source below. Use sources / exclude to narrow it.
| Source name | Platform or kind | Comment-enriched |
|---|---|---|
reddit | yes | |
twitter-ai-search | X / Twitter (Grok-backed AI synthesis) | no |
youtube | YouTube | yes |
tiktok | TikTok | yes |
instagram | yes | |
hackernews | Hacker News | yes |
polymarket | Polymarket prediction markets | no |
github | GitHub | yes |
threads | Threads | no |
pinterest | no | |
perplexity | Perplexity (web synthesis) | no |
tavily | Tavily (grounded web results) | no |
linkedin | no | |
rumble | Rumble | no |
tiktok-hashtag | TikTok hashtag search (hashtag mode) | yes |
instagram-hashtag | Instagram hashtag search (hashtag mode) | no |
youtube-hashtag | YouTube hashtag search (hashtag mode) | yes |
The last three fire only in hashtag mode. That is 14 base platforms plus 3 hashtag siblings, so up to 17 sources on a single hashtag-mode call.
Platform shorthands. A bare platform name resolves to its whole group. twitter and x both reach twitter-ai-search, and youtube, instagram and tiktok each cover their -hashtag lane too, so exclude=youtube cannot leak YouTube content back in through youtube-hashtag. Exact lane names stay individually addressable. An unknown name in sources or exclude is a 400 that enumerates the valid names, never a silent drop.
Keyword mode and hashtag mode
There is no user-facing mode switch. The planner decides:
- Keyword mode (default). A natural-language query fans out to the base keyword-search sources.
- Hashtag mode. The three hashtag siblings are added automatically in either of two cases. Explicit tag: the query is a single hashtag token, for example
query=%23thrifting, and the leading#is stripped before dispatch. Topic term: for a multi-word topic query the planner extracts a topic term (makeup tutorialsyields the tagmakeup) and appends a hashtag-sibling subquery at half weight, so hashtag signal never dominates fusion on an ambiguous query.
When the same TikTok, Instagram or YouTube post appears in both streams, fusion merges the two into one candidate.
If the planner times out or fails, explicit #tag queries still route to hashtag mode deterministically, and multi-word topic queries degrade to keyword-only.
The query value is passed to the planner as-is. There are no special search operators: no site:, no boolean AND/OR, no quoting. The single input with special meaning is a leading #.
Request parameters
| Param | Required | Type | Default | Description |
|---|---|---|---|---|
query | yes | string (1 to 512 chars) | none | The natural-language search query. A leading # puts the query into hashtag mode. |
lookback_days | no | integer (minimum 1) | 30 | Recency window in days. Zero or negative is rejected before billing; there is no upper bound, because this is a ranking signal rather than a hard filter and a very wide window is a degraded answer rather than an invalid request. Mutually exclusive with from_date / to_date. It sets the freshness curve and is forwarded to the sources whose upstream accepts date bounds, so an older item can still rank when nothing recent matches. Windows over 90 days degrade coverage on date-limited sources and emit a warning chunk. |
from_date | no | ISO YYYY-MM-DD | none | Lower bound of an explicit date window. Mutually exclusive with lookback_days. |
to_date | no | ISO YYYY-MM-DD | today | Upper bound of an explicit date window. Defaults to today when from_date is set alone. |
from and to are accepted as aliases for from_date and to_date, so a date window carried over from /v1/search/news (which spells them from / to) is honoured here rather than silently dropped. See One date vocabulary across the four lanes.
| sources | no | CSV string | all | Allowlist of source or platform names, for example reddit,youtube,hackernews. Mutually exclusive with exclude. |
| exclude | no | CSV string | none | Blocklist of source or platform names to skip. Mutually exclusive with sources. |
| include_transcripts | no | true / 1 | false | Opt-in spoken-word transcripts on the top 3 video results, so a video whose title never mentions your query but whose narration does still surfaces. |
Passing both sources and exclude returns 400 INVALID_REQUEST. Check data.sources_called (or the SSE done summary) against what you asked for.
Filter examples
# Narrow to specific sources
curl "https://www.socialcrawl.dev/v1/search/everywhere?query=apple+vision+pro+reviews&sources=reddit,youtube,hackernews" \
-H "x-api-key: YOUR_API_KEY"
# Exclude noisy sources
curl "https://www.socialcrawl.dev/v1/search/everywhere?query=nvidia+earnings&exclude=pinterest,polymarket" \
-H "x-api-key: YOUR_API_KEY"
# Explicit date window
curl "https://www.socialcrawl.dev/v1/search/everywhere?query=world+cup&from_date=2026-06-01&to_date=2026-06-30" \
-H "x-api-key: YOUR_API_KEY"
# Recency window
curl "https://www.socialcrawl.dev/v1/search/everywhere?query=nvidia+earnings&lookback_days=7" \
-H "x-api-key: YOUR_API_KEY"
# Hashtag mode (leading #, URL-encoded as %23)
curl "https://www.socialcrawl.dev/v1/search/everywhere?query=%23thrifting" \
-H "x-api-key: YOUR_API_KEY"In a real #thrifting call the plan intent resolves to how_to, and sources_called includes tiktok-hashtag, instagram-hashtag and youtube-hashtag alongside the base sources.
What you get back
By default (Accept: application/json) you get one envelope. data carries:
| Field | Type | Description |
|---|---|---|
query | string | The query as the planner saw it (# stripped for explicit-tag queries). |
plan | object | The resolved plan: intent, freshness_mode, cluster_mode, subqueries[], source_weights, notes[]. |
items | array | The ranked, deduped candidates (up to 40). Each carries scoring plus its source_items[]. |
items_by_source | object | The same items grouped by source name, for per-platform rendering. |
clusters | array | Near-duplicate and same-story groupings over items. |
sources_called | array | Every source the pipeline attempted this request. |
sources_failed | object ({ source: reason }) | Sources that failed or were pruned, mapped to a reason string. |
sources_succeeded | array | Sources that returned without failing. |
coverage | number (0 to 1) | sources_succeeded / sources_called. Drives the partial-refund decision. |
partial_failure | boolean | true when a partial-coverage refund fired this request. |
Real-people comments
For every result whose source exposes a comments endpoint, the pipeline fetches the top-scoring comments and attaches them at data.items[i].source_items[0].metadata.top_comments[]. No second API call.
Each TopComment has this shape:
{
score: number | null, // upvote / like / points count, source-specific
excerpt: string, // up to 300 chars, HTML stripped
author: string | null, // null when [deleted] / [removed]
url: string | null, // direct comment URL when the source exposes one
date: string | null // ISO timestamp when available
}Comments are sorted by score descending and capped at 5 per result, 300 characters per excerpt. Sources without a comments endpoint (twitter-ai-search, threads, pinterest, polymarket, perplexity, tavily, linkedin, rumble) render no top_comments.
Streaming
Add Accept: text/event-stream and you get a live stream instead of one envelope. First byte lands in well under a second, typically around 300ms on the meta chunk, and fast sources stream their items while slow ones are still running.
curl -N "https://www.socialcrawl.dev/v1/search/everywhere?query=nvidia+earnings" \
-H "x-api-key: YOUR_API_KEY" \
-H "Accept: text/event-stream"Each event is one data: <json>\n\n frame, discriminated by type. The order guarantees:
metais first, emitted as soon as the request is parsed. It carries the deterministic plan and the fullsources_plannedlist.source_startedfires once per source before that source's adapter runs. Pruned or failing sources still emitsource_started, then asource_prunedorsource_failed.source_prunedfires when the fast planner judged a source irrelevant for the query, and the source appears insources_pruned(never insources_failed, and never in the coverage denominator). The planner fires an abort on the source, but a source that had already answered before the abort landed still emits itsitemschunk, so treatsource_prunedas "do not rank this source" rather than as proof that no request was made.itemsyield in completion order, so fast sources land first.plan_refinedis conditional: emitted only when the LLM-refined plan differs from the deterministic one. No chunk if the LLM times out or returns the plan unchanged.ranked_partialstreams repeatedly as the reranker scores batches. Each chunk carries only the scores that settled since the previous one, so merge bycandidate_idrather than replacing your map.scoredandtotalcarry cumulative progress for a percentage indicator.comments_enrichedis per-candidate. Key oncandidate_idto merge comments into the row already on screen. These always land beforedone, and may interleave withranked_partial/ranked_final.doneis always last, carrying the run summary. On a thrown error you get a terminalerrorfollowed bydonewithrefunded: true.
Streaming responses are never cached, and a streaming request cannot poison a follow-up sync request's cache. The two are keyed separately.
Billing
Flat 20 credits per call, deducted up front. Two automatic refund paths:
| Situation | What you pay |
|---|---|
| Normal run, including one where some sources failed but coverage stayed at or above the floor | 20 credits |
| Some results, but a strict majority of the called sources failed | 10 credits (half refunded), and partial_failure is true |
| Every source failed or returned nothing, so there are zero ranked items | 0 credits (fully refunded). Sync returns 502 ZERO_RESULTS; SSE sends a terminal done with refunded: true |
The terminal done summary's credits_used and refunded fields are the source of truth for what you were actually charged. See Credits and Endpoint pricing.
Latency and failure modes
Streaming consumers see progress within about a second of the first source finishing. Sync consumers wait for the whole pipeline, typically 12 to 30 seconds.
- A source times out or errors: the pipeline continues and the source lands in
sources_failed. SSE gets asource_failedchunk. - A comment fetch fails for one result: non-fatal. That row just has no
top_comments. lookback_daysover 90: date-limited sources (tiktok,instagram,threads,pinterest,reddit) can under-cover the window, and SSE emits awarning.- Pinterest may return empty even on broad queries, because of an upstream vendor issue.
