API Reference
Access startup signal data programmatically. Search companies, retrieve detailed profiles, and query recent signals across all sources.
API access is available on all paid plans (Pro, Edge, and Scale), from the first payment: free trials don't include it. Manage your API key in Account settings.
Authentication
All API requests require a Bearer token in the Authorization header. Create your API key in Account settings.
curl https://cluster.vc/api/v1/companies \
-H "Authorization: Bearer YOUR_API_KEY"GET /api/v1/companies
Search and browse companies. Without a query, returns companies sorted by source diversity then recency.
Query parameters
| Parameter | Default | Description |
|---|---|---|
q | — | Search query. Omit to browse. At most 100
characters. % and _ match literally; *, commas, square brackets, control
characters and parentheses (except with match=exact) are ignored. |
page | 1 | Page number (positive integer). |
page_size | 50 | Results per page (max 100). |
match | contains | Match mode: contains, starts_with, ends_with, exact. |
scope | all | Search scope: all, name, domain. |
signal_type | — | Companies with a signal of any of these types (the signalType values),
comma-separated: launch, filing, batch, funding, domain_sale, domain_reg. |
country | — | Country codes, comma-separated. Up to 20 items, 2-50 characters each. |
region | — | Regions, comma-separated. Up to 20 items, 1-50 characters each. |
industry | — | Companies in any of these industries, comma-separated. Up to 20 items, 1-50 characters each. |
tld | — | Top-level domain, with or without the leading dot: io, .io, .co.uk. Matches the end of the
domain. |
recency | widest on your plan | Latest signal within: 24h, 7d, 30d, 90d, all. A window wider than your plan's history is narrowed to the widest
one it allows; the applied window is returned in meta.recency. |
A q over its length, a list item that is
outside its length, blank, or has quotes, backslashes, braces or control
characters, or a malformed tld returns 400 INVALID_PARAMS rather than being dropped.
Unknown parameters are ignored.
Example request
curl "https://cluster.vc/api/v1/companies?q=acme&page_size=10" \
-H "Authorization: Bearer YOUR_API_KEY" Response
{
"data": [
{
"id": 123,
"slug": "acme-corp",
"displayName": "Acme Corp",
"domain": "acme.com",
"website": "https://acme.com",
"country": "US",
"city": "San Francisco",
"description": "Developer tools for startups",
"industry": ["SaaS", "B2B"],
"regions": ["North America"],
"fundingTotalUsd": 1500000,
"firstSeenAt": "2024-01-15T00:00:00Z",
"lastSignalAt": "2024-03-10T12:00:00Z",
"sources": ["hn", "producthunt"],
"signalsCount": 5,
"freshness": "hot"
}
],
"meta": {
"page": 1,
"pageSize": 10,
"hasMore": true,
"recency": "30d"
}
}GET /api/v1/companies/[id]
Retrieve a single company with its full signal history. Signal detail depends on your plan tier (see Signal Gating).
Path parameters
| Parameter | Description |
|---|---|
id | Numeric company ID. |
Merged or renamed companies
When a company is merged into another or renamed, its old ID returns 301 with
error code MOVED, a Location header pointing at the current ID, and the current ID in error.successorId. Most HTTP clients follow the redirect; if you store company IDs,
replace the old one with successorId.
Example request
curl https://cluster.vc/api/v1/companies/123 \
-H "Authorization: Bearer YOUR_API_KEY" Response
{
"data": {
"id": 123,
"slug": "acme-corp",
"displayName": "Acme Corp",
"domain": "acme.com",
"website": "https://acme.com",
"country": "US",
"city": "San Francisco",
"description": "Developer tools for startups",
"industry": ["SaaS", "B2B"],
"regions": ["North America"],
"fundingTotalUsd": 1500000,
"firstSeenAt": "2024-01-15T00:00:00Z",
"lastSignalAt": "2024-03-10T12:00:00Z",
"sources": ["hn", "sec.gov"],
"signalsCount": 5,
"freshness": "hot",
"tags": ["show-hn"],
"signals": [
{
"id": 1,
"source": "hn",
"signalType": "launch",
"title": "Show HN: Acme Corp",
"sourceUrl": "https://news.ycombinator.com/item?id=12345",
"fundingAmountUsd": null,
"fundingStage": null,
"freshness": "hot",
"eventDate": "2024-03-10T11:40:00Z",
"detectedAt": "2024-03-10T12:00:00Z",
"meta": {
"points": 145,
"num_comments": 23
},
"gating": null
},
{
"id": 2,
"source": "sec.gov",
"signalType": "filing",
"title": "Form D: Acme Corp",
"sourceUrl": null,
"fundingAmountUsd": 1500000,
"fundingStage": null,
"freshness": "warm",
"eventDate": "2024-02-20T00:00:00Z",
"detectedAt": "2024-02-21T08:00:00Z",
"meta": {
"total_offering_amount": 1500000
},
"gating": {
"reason": "plan_detail",
"fields": ["meta", "fundingStage", "sourceUrl"],
"availableAt": null
}
}
]
}
}GET /api/v1/signals
Query recent signals across all sources. Returns signals in reverse
chronological order. Signals still within your plan's freshness delay
are left out until it has passed (a company's detail lists them, with gating.reason: freshness_delay), so every page holds signals you can use.
Query parameters
| Parameter | Default | Description |
|---|---|---|
page | 1 | Page number (positive integer). |
page_size | 50 | Results per page (max 100). |
source | — | Filter by source: hn, producthunt, sec.gov, yc, crypto_directory, tech.eu, domain_sale, domain_reg, or a group: funding (both funding sources), domain (both domain sources). |
freshness | — | Filter by freshness: hot, warm, cold. |
Example request
curl "https://cluster.vc/api/v1/signals?source=hn&freshness=hot" \
-H "Authorization: Bearer YOUR_API_KEY" Response
{
"data": [
{
"id": 1,
"source": "hn",
"signalType": "launch",
"title": "Show HN: Acme Corp",
"sourceUrl": "https://news.ycombinator.com/item?id=12345",
"fundingAmountUsd": null,
"fundingStage": null,
"freshness": "warm",
"eventDate": "2024-03-10T11:40:00Z",
"detectedAt": "2024-03-10T12:00:00Z",
"meta": {
"points": 145,
"num_comments": 23
},
"gating": null,
"company": {
"id": 123,
"slug": "acme-corp",
"displayName": "Acme Corp",
"domain": "acme.com",
"website": "https://acme.com",
"country": "US",
"city": "San Francisco"
}
}
],
"meta": {
"page": 1,
"pageSize": 50,
"hasMore": true
}
}Response Objects
Every field below is always present; a value that is missing or withheld
is null.
Within v1, fields are only ever added, never removed or renamed, so
ignore fields you don't recognise.
Company (search result)
| Field | Type | Description |
|---|---|---|
id | integer | Company ID. Use it as the key (slugs aren't unique). |
slug | string | URL slug of the company page. |
displayName | string | Company name. |
domain | string | null | Primary domain. |
website | string | null | Website URL. |
country | string | null | Country code. |
city | string | null | City. |
description | string | null | Short description. |
industry | string[] | null | Industries. |
regions | string[] | null | Regions. |
fundingTotalUsd | number | null | Total disclosed funding in USD. |
firstSeenAt | string | null | When the company was first seen (ISO 8601). |
lastSignalAt | string | null | Time of the latest signal (ISO 8601). |
sources | string[] | Sources with at least one signal for the company. |
signalsCount | integer | Number of signals. |
freshness | string | hot, warm, cold. |
Company (detail): every search result field, plus
| Field | Type | Description |
|---|---|---|
tags | string[] | null | Tags such as show-hn or form-d. |
signals | Signal[] | The company's most recent signals, newest first (up to 100; signalsCount has the total). |
Signal
| Field | Type | Description |
|---|---|---|
id | integer | Signal ID. |
source | string | One of: hn, producthunt, sec.gov, yc, crypto_directory, tech.eu, domain_sale, domain_reg. |
signalType | string | One of: launch, filing, batch, funding, domain_sale, domain_reg. |
title | string | Signal title. |
sourceUrl | string | null | Link to the source. Null for domain signals. |
fundingAmountUsd | number | null | Funding amount in USD. |
fundingStage | string | null | Funding round, e.g. seed. |
freshness | string | hot, warm, cold. |
eventDate | string | null | When the event happened (launch, filing, round, sale), ISO 8601. |
detectedAt | string | null | When the signal was detected, ISO 8601. |
meta | object | null | Source-specific details; keys per source below. Null when none or withheld. |
gating | object | null | Null, or why detail is withheld (see below). |
Signal in /api/v1/signals: every Signal field, plus
| Field | Type | Description |
|---|---|---|
company | object | The signal's company: id, slug, displayName, domain, website, country, city (as in a search result). |
gating
| Field | Type | Description |
|---|---|---|
reason | string | history_window (older than your plan's history window or undated (permanent)), freshness_delay (too recent for your plan, until availableAt), plan_detail (a detail level your plan doesn't include). |
fields | string[] | Fields withheld: null, or for meta, trimmed to fewer keys; meta.<key> for a single meta key (e.g. meta.venue). |
availableAt | string | null | freshness_delay only: when full detail becomes available (ISO 8601). |
meta keys by source
meta holds only these keys, and only those with a value. A domain sale's venue is a
known marketplace name or "Undisclosed", and a sale price under
$2,000 comes as sale_price_under: 2000 instead of sale_price. investors is a list of names. For dates, prefer the signal's eventDate.
| Source | meta keys |
|---|---|
hn | points, num_comments, hn_url, domain, description |
producthunt | domain, description |
sec.gov | filing_date, entity_type, jurisdiction, industry_group, total_offering_amount, total_amount_sold, minimum_investment, securities_types, exemption_type, date_of_first_sale, num_investors, revenue_range, has_non_accredited_investors |
yc | batch, status, industries, domain, description |
crypto_directory | domain, description |
tech.eu | date_published, funding, stage, investors, website |
domain_sale | domain_sold, sale_price, venue, date, sale_price_under |
domain_reg | domain_name, registration_date, is_fresh |
Rate Limits
API rate limits are enforced per account (shared by all your API keys)
on a daily sliding window. Limits vary by plan. A query (one set of
filters, paged with page and page_size)
reaches up to the plan's results per query; a page past them returns PAGE_LIMIT_EXCEEDED.
Each plan also has a daily data budget per account, shared with the web
app and CSV export. Rows per day counts every list row delivered: /api/v1/companies and /api/v1/signals rows, web search results and CSV rows. Company details count each /api/v1/companies/{id} response and each company page on the web. A page is served in full while
rows are left, so the last page of the day may pass the budget by up to one
page. Once a budget is spent, requests return 429 ROW_BUDGET_EXCEEDED or DETAIL_BUDGET_EXCEEDED with Retry-After: rows and daily details reset at 00:00 UTC, the weekly details on
Monday at 00:00 UTC.
| Plan | Daily Requests | Companies per Query | Signals per Query | Rows per Day | Company Details per Day / Week |
|---|---|---|---|---|---|
| Pro | 20 | 50 | 50 | 500 | 30 / 100 |
| Edge | 200 | 500 | 500 | 2,000 | 100 / 400 |
| Scale | 1,000 | 2,000 | 2,000 | 4,000 | 300 / 1,000 |
Rate limit headers
Responses to an authenticated request include these headers, errors
included. Repeated 429s after the quota runs out, and a 503 when the
quota couldn't be checked, may come without them. List responses also
carry the RowBudget-* set, company detail responses the DetailBudget-* set (the company details budget that runs out first):
| Header | Description |
|---|---|
RateLimit-Limit | Maximum requests allowed in the current window. |
RateLimit-Remaining | Remaining requests in the current window. |
RateLimit-Reset | Seconds until the rate limit window resets. |
RowBudget-Limit | Your plan's rows per day. |
RowBudget-Remaining | Rows left today, after this response. |
RowBudget-Reset | Seconds until the row budget resets (00:00 UTC). |
DetailBudget-Limit | Your plan's company details per day, or per week once the week has fewer left. |
DetailBudget-Remaining | Company details left after this response, today or this week, whichever is fewer. |
DetailBudget-Reset | Seconds until that budget resets (00:00 UTC; the week on Monday). |
Signal Gating
Signal detail varies by plan. A withheld field is null, and
the signal's gating object says why and which fields (see Response Objects). In a company's detail, signals newer than your plan's freshness
delay come with freshness_delay and the time detail becomes available, and signals older than your plan's
history window (or without a date) come with history_window; /api/v1/signals leaves both out. Detail levels per signal type:
Detail levels by plan
| Signal Type | Pro | Edge | Scale |
|---|---|---|---|
| Launches | full | full | full |
| SEC Filings | amount_only | full | full |
| Funding | amount_only | full | full |
| YC Batches | amount_only | full | full |
| Domain | type_only | full | full |
| Domain Display | full | full | full |
Freshness delay and history window
| Plan | Freshness Delay | History Window |
|---|---|---|
| Pro | 2-day delay | 30 days |
| Edge | 6-hour delay | 90 days |
| Scale | 1-hour delay | Full access |
Errors
All errors return a JSON body with an error object containing a machine-readable code and a
human-readable message.
| HTTP Status | Code | Description |
|---|---|---|
| 401 | INVALID_API_KEY | Missing or invalid API key. |
| 403 | NO_ACTIVE_PLAN | API key valid but no active paid plan (or still on a free trial). |
| 400 | INVALID_PARAMS | Invalid query parameters. |
| 404 | NOT_FOUND | Requested resource does not exist. |
| 301 | MOVED | Company merged or renamed: successorId holds its current ID (see Company Detail). |
| 403 | PAGE_LIMIT_EXCEEDED | The page starts past your plan's results per query. |
| 429 | RATE_LIMITED | Daily request quota exceeded. |
| 429 | ROW_BUDGET_EXCEEDED | Today's rows are spent (shared with the web app and CSV
export). Retry after Retry-After seconds. |
| 429 | DETAIL_BUDGET_EXCEEDED | Today's or this week's company details are spent (shared with
the web app). Retry after Retry-After seconds. |
| 503 | SERVICE_UNAVAILABLE | Temporary outage. Retry later (after Retry-After seconds when present). |
Example error response
{
"error": {
"code": "RATE_LIMITED",
"message": "Daily API request limit exceeded. Resets in 14400 seconds."
}
}Ready to integrate?
Every paid plan includes API access. API access starts with your first payment, when the free trial ends.
Get notified