API Reference
Machine-readable access to ClusterSignal's full signal set — insider clusters, congressional trading clusters, cross-source alignment events, trademark filings, 13D/13G activist stakes, 13F fund position changes, government contract awards, and FDA/clinical-trial catalysts. Three endpoints: a paginated cross-type list for incremental polling, an enriched detail endpoint for individual insider-cluster signals, and a per-ticker summary across every signal type.
Base URL
https://www.clustersignal.app/api/v1
Always use the www. subdomain. clustersignal.app issues a 308 redirect that many HTTP clients drop the Authorization header on.
Authentication
All requests require an API key passed as a Bearer token. Keys require an active API subscription ($19/mo) and are generated at /account/api-keys.
Authorization: Bearer cs_live_<key>
Key format: cs_live_ prefix + 64 hex chars (256-bit random)
Storage: Raw key is shown once at generation and never stored. Only a SHA-256 hash is kept server-side.
Limit: 3 active keys per account. Revoke unused keys to free slots.
Rate Limits
60 requests per minute, per API key.
Enforced atomically across all serverless instances via Postgres. Exceeding the limit returns 429.
The list endpoint is DB-only and safe to poll every 60 seconds. The detail endpoint makes external API calls (Yahoo Finance, SEC EDGAR) — poll it sparingly.
GET /api/v1/signals
Returns a paginated, cross-type list of signals ordered by detected_at ascending. DB-only — safe to poll frequently.
Query Parameters
sincestringISO 8601 timestamp. Returns signals detected after this time. Use next_since from the previous response to page forward.
limitintegerNumber of results. Range: 1–500. Default: 100. Applies to the merged, cross-type result — not per type.
signal_typestringComma-separated list of types to include — see Signal Types below. Default: insider_cluster only (unchanged from v1.0, so existing integrations see no shape change unless they opt in).
tickerstringFilter to one ticker symbol. Applies across all requested signal_types.
directionstringinsider_cluster only. Filter by trade direction. Values: buy | sell
min_scoreintegerinsider_cluster only. Minimum cluster score (0–100). Filters out low-conviction signals.
min_gradestringinsider_cluster only. Minimum grade. Values: A | B | C. Equivalent to min_score 80 | 50 | 0.
Response
{
"schema_version": "1.2",
"server_time": "2026-07-21T12:25:21.588Z",
"count": 3,
"next_since": "2026-06-16T18:07:38.445456+00:00",
"signals": [ /* Signal objects — shape varies by signal_type, see Signal Types */ ]
}schema_versionstring"1.2" as of this release. Bumped on additive changes.
countintegerNumber of signals in this response.
next_sincestring|nulldetected_at of the last signal in this batch, across all requested types. Pass as since on the next poll. Null when the response is empty.
signalsarrayArray of Signal objects, chronologically merged across every requested signal_type. Each object's signal_type field tells you which shape it is.
When requesting multiple signal_types, each type is fetched independently up to limit and the results are merged by detected_at before truncating to the page size, so pagination stays chronologically correct across types.
GET /api/v1/signals/:id
Returns a single signal with full enrichment: live price data, earnings proximity, dilution risk, filing speed, and insider track records. Makes external API calls — use only for signals that pass your first-pass filter.
GET /api/v1/signals/6251ee0d-c83b-452c-8531-85c951b769a6
Response
{
"schema_version": "1.2",
"server_time": "2026-07-21T12:25:21.588Z",
"signal": {
/* All list Signal fields plus: */
"flags": {
"earnings_proximity_days": null, // same value as the list endpoint's field, null if unknown
"dilution_risk": false, // >3 filings on same day = potential offering signal
"volume_spike": false, // today's volume > 2× 20-day average
"filing_speed_same_day_pct": 0 // fraction of insiders who filed same day as trade
},
"price_context": {
"company_name": "AppFolio, Inc.",
"sector": "Information Technology",
"current_price": 155.145,
"price_change_pct": null,
"market_cap": null,
"week_52_high": 326.04,
"week_52_low": 142.56,
"price_chart": {
"timestamps": [ /* Unix timestamps */ ],
"closes": [ /* Close prices */ ],
"volumes": [ /* Daily volumes */ ]
},
"price_return": {
"priceAtCluster": 159.68,
"return_30d": null, // null until enough time has passed
"return_60d": null,
"return_90d": null
},
"win_rate": null,
"signal_summary": "Signal Strength: ...",
"insider_track_records": [
{
"insider_cik": "2082975",
"insider_name": "Rigler Don",
"officer_title": null,
"trade_direction": "buy", // this insider's own direction in this cluster — "first_time_buyer" is only ever true for "buy"
"is_first_time_buyer": true,
"prior_trades": [] // prior buys for a "buy" insider, prior sales for a "sell" insider — never mixed
}
]
}
}
}Returns 404 with {"error":"not_found"} for unknown IDs.
GET /api/v1/tickers/:symbol/summary
Everything known about one ticker across every signal type, in a single response. DB-only — no external API calls, safe to poll.
GET /api/v1/tickers/PLTR/summary
Response
{
"schema_version": "1.2",
"server_time": "2026-07-21T12:25:21.588Z",
"ticker": "PLTR",
"company_name": null,
"sector": null,
"insider_clusters": [ /* insider_cluster signals, up to 100 */ ],
"congressional_clusters": [ /* congressional_cluster signals, up to 100 */ ],
"aligned_signals": [ /* aligned_signal signals, up to 100 */ ],
"trademark_filings": [ /* trademark_filing signals, up to 100 */ ],
"activist_stakes": [ /* activist_stake signals, up to 100 */ ],
"fund_positions": [ /* fund_position signals, up to 100 */ ],
"gov_contracts": [
{
"signal_type": "gov_contract",
"id": "40a0e34d-feb4-43ab-80d3-029512312c3c",
"ticker": "PLTR",
"recipient_name": "PALANTIR TECHNOLOGIES INC.",
"awarding_agency": "Department of Homeland Security",
"award_amount": 45848616.8,
"start_date": "2026-06-26",
"aligned_with_congressional_trade": false,
"usaspending_url": "https://www.usaspending.gov/award/CONT_AWD_...",
"detected_at": "2026-07-20T13:04:41.199363+00:00"
}
],
"fda_catalysts": [ /* fda_catalyst signals, up to 100 */ ]
}company_namestring|nullFrom the enrichment cache. Null if not yet cached.
sectorstring|nullFrom the enrichment cache. Null if not yet cached.
*arrayEvery other top-level field is an array of Signal objects of that type (see Signal Types), each capped at 100 rows, most recent first by detected_at. Empty array — not an error — when there's nothing of that type for this ticker.
Unknown tickers return 200 with every array empty, not a 404 — there's no canonical ticker list to validate against.
Signal Object
Fields present on every signal (list and detail).
signal_typestringAlways "insider_cluster".
idstringUUID. Stable — use for deduplication and detail lookups.
tickerstringStock ticker symbol.
company_namestring|nullCompany name from the enrichment cache. Null if not yet cached.
sectorstring|nullGICS sector from the enrichment cache. Null if not yet cached.
directionstring"buy" or "sell".
gradestring"A" (score ≥ 80), "B" (≥ 50), or "C" (< 50). Final at detection — not updated retroactively.
scoreinteger0–100 conviction score. 0 on clusters detected before scoring was live.
score_breakdownobjectPer-dimension score components. Keys: insider_count, capital, seniority, win_rate (always 0), velocity, filing_speed.
window_startstringYYYY-MM-DD. First transaction date in the cluster window.
window_endstringYYYY-MM-DD. Last transaction date in the cluster window.
detected_atstringISO 8601. When the cluster was first detected by the poller.
aggregate_value_usdnumberSum of transaction values across all filings in the cluster.
insider_countintegerNumber of distinct insiders in the cluster.
is_short_radarbooleanTrue when direction=sell, insider_count ≥ 3, and aggregate_value_usd ≥ $250k. A higher-conviction sell signal worth extra scrutiny.
earnings_proximity_daysinteger|nullAdded in 1.2. Days from window_end to the ticker's next known earnings date (negative = earnings already passed since detection). Null if no earnings date is known within -30/+120 days. DB-only lookup against a daily-synced calendar — no external call, safe on the list endpoint.
filingsarrayIndividual Form 4 filings in the cluster.
edgar_urlstring|nullSEC EDGAR archives URL for the first filing. Null if CIK or accession number unavailable.
Filing Object
insider_namestringFull name as reported on Form 4.
titlestring|nullOfficer title (e.g. CEO, CFO). Null if not reported.
transaction_codestringSEC transaction code. Common: "S" (open-market sale), "P" (open-market purchase), "A" (grant/award).
sharesnumber|nullNumber of shares transacted.
pricenumber|nullPrice per share.
value_usdnumber|nullTotal transaction value (shares × price).
transaction_datestringYYYY-MM-DD.
is_10b5_planbooleanTrue if the trade was executed under a pre-arranged 10b5-1 plan. Reduces signal strength.
is_cashless_exercisebooleanTrue if the sale was a cashless option exercise (sell-to-cover). Reduces signal strength.
Signal Types
Every object in signals carries signal_type, id, ticker, and detected_at — the fields below that are type-specific. ticker is null when the underlying filer/recipient/sponsor couldn't be matched to a public company (deliberately conservative — an exact-normalized-name match only, never fuzzy, to avoid misattributing a signal to the wrong ticker).
congressional_cluster
2+ members of Congress trading the same ticker/direction within a 60-day window. Source: House disclosures.
directionstring"buy" or "sell".
member_countintegerDistinct members in the cluster.
window_startstringYYYY-MM-DD
window_endstringYYYY-MM-DD
total_amount_lownumber|nullSum of disclosed range lower bounds.
total_amount_highnumber|nullSum of disclosed range upper bounds.
membersarrayPer-member detail: name, party, state, source, committees, trade_date.
has_aligned_signalbooleanTrue if this cluster also lines up with a corporate insider cluster — see aligned_signal.
aligned_signal
Cross-source corroboration — two independent signal sources point to the same ticker around the same time. A stronger signal than either source alone.
alignment_typestring"political_corporate" (a congressional cluster lines up with a corporate insider cluster) or "contract_congressional" (a gov contract award lines up with a congressional trade in the same ticker by a member on a relevant oversight committee, within 60 days).
detailsobjectShape depends on alignment_type — political_corporate carries the congressional_cluster fields (direction, member_count, window_start/end, members); contract_congressional carries the gov_contract fields (recipient_name, awarding_agency, award_amount, start_date, aligned_trades).
trademark_filing
New USPTO trademark applications from publicly traded companies — can signal unannounced products/brands.
mark_textstring|nullThe trademark text/name, when disclosed.
applicant_namestringFiling entity name as reported to USPTO.
goods_services_descriptionstring|nullWhat the mark covers.
filing_datestringYYYY-MM-DD
nice_classesarrayNice Classification codes (goods/services categories).
activist_stake
SEC Schedule 13D/13G beneficial-ownership filings — someone crossing the 5% ownership threshold.
filing_typestring"13D" (activist intent) or "13G" (passive investor).
is_amendmentbooleanTrue for a follow-up amendment rather than the original filing.
filer_namestringThe reporting person/entity taking the stake.
issuer_namestringThe company whose stock was acquired.
percent_ownershipnumber|nullPercent of class owned, as reported.
shares_ownednumber|nullAggregate shares beneficially owned.
filed_datestringYYYY-MM-DD
edgar_urlstringSEC EDGAR archives URL for the filing.
aligned_with_insider_clusterbooleanTrue if this filing landed on a ticker with an active insider buy cluster (last 90 days).
fund_position
Quarter-over-quarter position changes from ~20 tracked notable 13F filers (Berkshire Hathaway, Renaissance Technologies, Citadel, etc.) — new positions, full exits, and adds/trims of 20%+.
filer_namestringTracked fund/manager name.
change_typestring"new" | "exit" | "increase" | "decrease".
period_of_reportstringQuarter-end date for this filing, YYYY-MM-DD.
prior_periodstring|nullQuarter-end date this was compared against.
valuenumber|nullPosition value this quarter (USD). Null for exit.
prior_valuenumber|nullPosition value prior quarter (USD). Null for new.
sharesnumber|nullShares held this quarter. Null for exit.
prior_sharesnumber|nullShares held prior quarter. Null for new.
pct_changenumber|nullValue change fraction (0.25 = +25%). Null for new/exit.
gov_contract
New federal prime contract awards ≥ $10M from USAspending.gov, matched to a public recipient where possible.
recipient_namestringContract recipient as reported to USAspending.
awarding_agencystringFederal agency awarding the contract.
award_amountnumberAward amount in USD.
start_datestringYYYY-MM-DD
aligned_with_congressional_tradebooleanTrue if a member on a relevant oversight committee traded this ticker within 60 days of the award — see aligned_signal for the detail.
usaspending_urlstringUSAspending.gov award detail URL.
fda_catalyst
Forward-looking Phase 3 clinical trial readouts for publicly traded biotechs, from clinicaltrials.gov. PDUFA dates are not tracked — there's no official structured source for them.
sponsor_namestringLead sponsor (industry) as reported to clinicaltrials.gov.
titlestringTrial brief title.
phasestring|nulle.g. "PHASE3" or "PHASE2, PHASE3" for combined trials.
conditionstring|nullUp to 3 conditions/indications, comma-separated.
statusstring"RECRUITING" | "ACTIVE_NOT_RECRUITING" | "ENROLLING_BY_INVITATION".
primary_completion_datestring|nullYYYY-MM-DD — the key readout date. Day precision isn't always disclosed (defaults to the 1st of the month).
completion_datestring|nullYYYY-MM-DD — full trial completion, typically later than primary completion.
study_urlstringclinicaltrials.gov study page.
Errors
All error responses use the same shape:
{ "error": "human-readable description" }| Status | error value | Meaning |
|---|---|---|
| 401 | "unauthorized" | No Authorization header, or key not found / revoked. |
| 403 | "API access requires..." | Key is valid but access has been disabled for the associated account. |
| 404 | "not_found" | Signal ID does not exist (detail endpoint only). |
| 429 | "Rate limit exceeded..." | 60 req/min per key exceeded. Back off and retry after the current minute. |
| 500 | varies | Server error. Retry with exponential backoff. |
Pagination
The list endpoint uses a since cursor. Results are ordered by detected_at ASC with id as a stable tiebreaker. On each response, save next_since and pass it as since on the next poll. No results means you are caught up.
// Poll loop (pseudocode)
let since = null
while (true) {
const url = since
? `/api/v1/signals?since=${since}&limit=100`
: '/api/v1/signals?limit=100'
const res = await fetch(url, { headers })
const body = await res.json()
for (const signal of body.signals) {
process(signal)
}
if (body.count === 0) {
await sleep(60_000) // caught up — wait a minute
} else {
since = body.next_since
// more pages may exist — loop immediately
}
}Examples
List signals (curl)
curl -s "https://www.clustersignal.app/api/v1/signals?limit=10&direction=buy&min_grade=B" \ -H "Authorization: Bearer cs_live_<your_key>"
Fetch signal detail (curl)
curl -s "https://www.clustersignal.app/api/v1/signals/<signal_id>" \ -H "Authorization: Bearer cs_live_<your_key>"
List signals (JavaScript)
const BASE = 'https://www.clustersignal.app/api/v1'
const KEY = process.env.CLUSTERSIGNAL_API_KEY
async function fetchSignals(since?: string) {
const url = new URL(`${BASE}/signals`)
url.searchParams.set('limit', '100')
if (since) url.searchParams.set('since', since)
const res = await fetch(url, {
headers: { Authorization: `Bearer ${KEY}` },
})
if (!res.ok) throw new Error(`${res.status}: ${(await res.json()).error}`)
return res.json()
}
async function fetchDetail(id: string) {
const res = await fetch(`${BASE}/signals/${id}`, {
headers: { Authorization: `Bearer ${KEY}` },
})
if (!res.ok) throw new Error(`${res.status}: ${(await res.json()).error}`)
return res.json()
}Filter for high-conviction buys only
GET /api/v1/signals?direction=buy&min_grade=A&limit=50
Filter for short-radar sells
# Fetch sells, then filter is_short_radar === true client-side GET /api/v1/signals?direction=sell&limit=100 # Or use min_score to tighten the set GET /api/v1/signals?direction=sell&min_score=50&limit=100
Poll every new signal type at once
GET /api/v1/signals?signal_type=congressional_cluster,aligned_signal,trademark_filing,activist_stake,fund_position,gov_contract,fda_catalyst&limit=100
One ticker, one call, every signal type
curl -s "https://www.clustersignal.app/api/v1/tickers/PLTR/summary" \ -H "Authorization: Bearer cs_live_<your_key>"
Changelog
v1.22026-07-21Added earnings_proximity_days to the insider_cluster Signal Object — now available on GET /signals (list), not just the detail endpoint's flags. DB-only lookup against a daily-synced earnings calendar, so the list endpoint stays safe to poll frequently.v1.12026-07-21Added signal_type and ticker params to GET /signals — 7 new signal types: congressional_cluster, aligned_signal, trademark_filing, activist_stake, fund_position, gov_contract, fda_catalyst. Default behavior (no signal_type) is unchanged — insider_cluster only. Added GET /tickers/:symbol/summary.v1.02026-06-29Initial release. GET /signals and GET /signals/:id.Additive changes (new optional fields, new optional query params) happen within v1 and bump schema_version. Breaking changes go to /api/v2/.