{"openapi":"3.1.0","info":{"title":"Rocketlist public API","version":"2026-08-29","description":"The unauthenticated read endpoints of rocketlist.ai. No API key, no account, no OAuth. Rocketlist also runs a public read-only MCP server at https://rocketlist.ai/mcp (discovery document at https://rocketlist.ai/.well-known/mcp.json), which covers job and company search with less bookkeeping than these endpoints. Per-user and cron endpoints exist but are authenticated and are not described here.","contact":{"url":"https://rocketlist.ai"}},"servers":[{"url":"https://rocketlist.ai"}],"paths":{"/api/jobs":{"get":{"operationId":"searchJobs","summary":"Search active job postings.","description":"The catalog search that backs /jobs. Returns active, non-duplicate postings that have been re-crawled recently enough to be considered live.","parameters":[{"name":"q","in":"query","required":false,"description":"Free-text query over title, company, location and category.","schema":{"type":"string"}},{"name":"filters","in":"query","required":false,"description":"Comma-separated toggles drawn from `new`, `salary` and `remote`. Unrecognised values are ignored rather than rejected.","schema":{"type":"string","examples":["remote","remote,salary"]}},{"name":"seniority","in":"query","required":false,"description":"Seniority levels, from /api/jobs/filters. Comma-separated for multiple values.","schema":{"type":"string"}},{"name":"city","in":"query","required":false,"description":"Cities, from /api/jobs/filters. Comma-separated for multiple values.","schema":{"type":"string"}},{"name":"category","in":"query","required":false,"description":"Job categories, from /api/jobs/filters. Comma-separated for multiple values.","schema":{"type":"string"}},{"name":"industry","in":"query","required":false,"description":"Company industries, from /api/jobs/filters. Comma-separated for multiple values.","schema":{"type":"string"}},{"name":"stage","in":"query","required":false,"description":"Company funding stages, from /api/jobs/filters. Comma-separated for multiple values.","schema":{"type":"string"}},{"name":"founded","in":"query","required":false,"description":"Company founding-year buckets, from /api/jobs/filters. Comma-separated for multiple values.","schema":{"type":"string"}},{"name":"funding","in":"query","required":false,"description":"Total-raised buckets, from /api/jobs/filters. Comma-separated for multiple values.","schema":{"type":"string"}},{"name":"investor","in":"query","required":false,"description":"Investor names, from /api/jobs/filters. Comma-separated for multiple values.","schema":{"type":"string"}},{"name":"sort","in":"query","required":false,"description":"Sort order. `score` is a signed-in-only ordering and falls back to `date` here.","schema":{"type":"string","enum":["date","salary","score"],"default":"date"}},{"name":"page","in":"query","required":false,"description":"1-based page number.","schema":{"type":"integer","minimum":1,"default":1}},{"name":"limit","in":"query","required":false,"description":"Page size. Values above 100 are clamped to 100.","schema":{"type":"integer","minimum":1,"maximum":100,"default":10}},{"name":"offset","in":"query","required":false,"description":"Selects a PAGE, not a row. When present it overrides `page`, as `floor(offset / limit) + 1`, so an offset that is not a multiple of `limit` is rounded down to the start of the page containing it. Verified against production 2026-08-29: `limit=10&offset=15` answers `page: 2` and returns the same rows as `limit=10&offset=10`, not rows 15-24. Send a multiple of `limit`, or use `page`. Above 10000 the request is rejected with 400.","schema":{"type":"integer","minimum":0,"maximum":10000}}],"responses":{"200":{"description":"A page of job postings. `jobs` honours every filter, but `total` and `totalPages` do NOT when `investor` is set: that facet is applied after the counted query, so the count is not investor-aware and over-reports. Verified 2026-08-29, `investor=Sequoia Capital` and `investor=NotARealInvestorXyz` both report `total: 109808`, while the first returns rows and the second returns none. Paginate an investor search until `jobs` comes back empty rather than trusting `totalPages`.","content":{"application/json":{"schema":{"type":"object","properties":{"jobs":{"type":"array","items":{"type":"object","additionalProperties":true,"description":"A job posting. The field set is not frozen and can grow; treat unknown keys as additive."}},"total":{"type":"integer"},"page":{"type":"integer"},"limit":{"type":"integer"},"totalPages":{"type":"integer"}},"required":["jobs","total","page","limit","totalPages"]}}}},"400":{"description":"Error.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited. Carries a `Retry-After` header and a `retry_after_seconds` body field.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"retry_after_seconds":{"type":"integer"}},"required":["error","retry_after_seconds"]}}}},"500":{"description":"Error.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"The catalog read failed, in one of two shapes. When the catalog is unavailable the body keeps the 200 envelope with an empty `jobs` array plus `degraded: true`, `degradationReason` and `retry_after_seconds`. When the search times out the body is `{error, retry_after_seconds}` instead. Check for `jobs` before assuming either.","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/api/jobs/filters":{"get":{"operationId":"getJobFilterOptions","summary":"List the values accepted by the /api/jobs filter parameters.","description":"The values `/api/jobs` accepts for the matching parameter. Use them rather than guessing, but do not assume they are all matched the same way. `seniority`, `category`, `industry` and `stage` are compared as exact stored values. `investor` is the one to be careful with: the ROW's investor list is canonicalised before comparison but YOUR value is not, so an alias silently matches nothing. Send exactly what `investorOptions` returns. Verified 2026-08-29, `/api/jobs?investor=Andreessen Horowitz` returns rows while `?investor=a16z` returns none, and `a16z` is not in `investorOptions`. `/api/companies` does canonicalise the request value, so the two endpoints differ here. `city` values are normalised to canonical city names and matched against the job city only — verified 2026-08-29, `city=New York`, `city=NYC` and `city=New York City` all return 13,253 — and the country field is never matched, so a city filter does not widen to its country. `founded` and `funding` are labels for derived numeric ranges (\"2015-2019\", \"$10M-$50M\") applied as range comparisons rather than values stored on any row, and a job whose company has no founding year or no total-raised figure is excluded outright when you filter on them.","responses":{"200":{"description":"Available filter values.","content":{"application/json":{"schema":{"type":"object","properties":{"seniorityOptions":{"type":"array","items":{"type":"string"}},"cityOptions":{"type":"array","items":{"type":"string"}},"categoryOptions":{"type":"array","items":{"type":"string"}},"industryOptions":{"type":"array","items":{"type":"string"}},"stageOptions":{"type":"array","items":{"type":"string"}},"foundedYearOptions":{"type":"array","items":{"type":"string"}},"fundingOptions":{"type":"array","items":{"type":"string"}},"investorOptions":{"type":"array","items":{"type":"string"}}}}}}},"503":{"description":"Error.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/api/public/jobs/{id}":{"get":{"operationId":"getJob","summary":"Get one job posting.","description":"Accepts either the canonical slug (`company-title-atsJobId`) or the legacy UUID. Closed postings still resolve, and carry `listing_closed: true`. Read that flag narrowly. It is one predicate with two triggers — `is_active` is false, OR the row has no `date_scraped` — and it says nothing about why. Deduplication deactivates rows too, and it dominates: measured 2026-08-29, 613,950 of the 734,571 rows with `is_active` false were duplicates, every duplicate row is deactivated, and no row at all was missing a `date_scraped`. Some closures ARE employer-confirmed — a cron probes the employer URL of active non-duplicate listings and deactivates the ones it finds definitively dead — but they are the minority, so a closed row cannot be read as \"the employer withdrew this role\". Of the 613,950 closed duplicates, 188,979 point at a canonical row that is still active, 138,051 have no canonical recorded at all, and the rest point at a canonical that is itself closed. Counts drift daily.","parameters":[{"name":"id","in":"path","required":true,"description":"Job slug or UUID.","schema":{"type":"string"}}],"responses":{"200":{"description":"The job posting, including its description and apply URL.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"A job posting. The field set is not frozen and can grow; treat unknown keys as additive."}}}},"400":{"description":"Error.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"Error.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"500":{"description":"Error.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/api/companies":{"get":{"operationId":"listCompanies","summary":"List companies with at least one active job.","description":"Ordered by active job count descending, then name. WHETHER the response is paginated at all is conditional, and that is the trap here. Send `limit` on every request and none of it matters. The exact rule, verified against production 2026-08-29: with `investor` set, the response is paginated only when `page` or `limit` is also present, so `investor` alone returns every match (198 for Sequoia Capital) and `investor` plus `q` still returns every match (167). Without `investor`, pagination is triggered by `page`, `limit`, or a `q` that survives normalization — and a `q` with no ASCII letter or digit left in it does not survive, so `?q=!!!` and `?q=...` each return all 4,966 companies as a bare `/api/companies` does, while `?q=anduril` returns 1 and `?q=!!!&limit=5` returns 5. A request that triggers nothing returns the ENTIRE hiring-company set, several thousand rows in one response. Once paginated, the page size is `limit` when given and 20 when not.","parameters":[{"name":"q","in":"query","required":false,"description":"Company name search. `query` is accepted as an alias. The value is normalized before use: comma, parentheses, period, colon, double quote, apostrophe, backtick and backslash all become spaces, runs of whitespace collapse, and the result is trimmed to 80 characters and then discarded entirely unless an ASCII letter or digit still remains. The test is /[A-Za-z0-9]/, so a query written only in non-Latin script is discarded too. A discarded `q` is treated as absent, including for the pagination rule above.","schema":{"type":"string"}},{"name":"investor","in":"query","required":false,"description":"Investor names. Common aliases resolve (a16z, Sequoia China). Comma-separated for multiple values.","schema":{"type":"string"}},{"name":"page","in":"query","required":false,"description":"1-based page number.","schema":{"type":"integer","minimum":1,"default":1}},{"name":"limit","in":"query","required":false,"description":"Page size. Values above 100 are clamped to 100. Omitting it falls back to 20 only once the response is paginated at all, which is not unconditional: see the operation description.","schema":{"type":"integer","minimum":1,"maximum":100}}],"responses":{"200":{"description":"Companies. There is no total in this envelope.","content":{"application/json":{"schema":{"type":"object","properties":{"companies":{"type":"array","items":{"type":"object","additionalProperties":true,"description":"A company record. The field set is not frozen and can grow; treat unknown keys as additive."}}},"required":["companies"]}}}},"503":{"description":"Error.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/api/companies/{slug}/jobs":{"get":{"operationId":"getCompanyJobs","summary":"Get one company and a page of its active jobs.","parameters":[{"name":"slug","in":"path","required":true,"description":"Company slug, as used at /companies/{slug}.","schema":{"type":"string","pattern":"^[a-z0-9-]+$"}},{"name":"limit","in":"query","required":false,"description":"Page size. Values above 100 are clamped to 100.","schema":{"type":"integer","minimum":0,"maximum":100,"default":20}},{"name":"offset","in":"query","required":false,"description":"Row offset. Values above 10000 are clamped.","schema":{"type":"integer","minimum":0,"maximum":10000,"default":0}}],"responses":{"200":{"description":"The company plus its jobs. `jobs` is the display projection and `jobRows` is the same page in raw catalog-column form.","content":{"application/json":{"schema":{"type":"object","properties":{"company":{"type":"object","additionalProperties":true,"description":"A company record. The field set is not frozen and can grow; treat unknown keys as additive."},"jobs":{"type":"array","items":{"type":"object","additionalProperties":true,"description":"A job posting. The field set is not frozen and can grow; treat unknown keys as additive."}},"jobRows":{"type":"array","items":{"type":"object","additionalProperties":true,"description":"A job posting. The field set is not frozen and can grow; treat unknown keys as additive."}},"total":{"type":"integer"},"limit":{"type":"integer"},"offset":{"type":"integer"}},"required":["company","jobs","total","limit","offset"]}}}},"404":{"description":"Error.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"500":{"description":"Error.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Error.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/api/investors/{slug}/companies":{"get":{"operationId":"getInvestorPortfolioCompanies","summary":"Get the hiring companies backed by one investor.","parameters":[{"name":"slug","in":"path","required":true,"description":"Investor slug, as used at /investors/{slug}.","schema":{"type":"string","pattern":"^[a-z0-9-]+$"}},{"name":"limit","in":"query","required":false,"description":"Page size. Values above 100 are clamped to 100.","schema":{"type":"integer","minimum":0,"maximum":100,"default":30}},{"name":"offset","in":"query","required":false,"description":"Row offset. Values above 10000 are clamped.","schema":{"type":"integer","minimum":0,"maximum":10000,"default":0}}],"responses":{"200":{"description":"A page of portfolio companies.","content":{"application/json":{"schema":{"type":"object","properties":{"companies":{"type":"array","items":{"type":"object","additionalProperties":true,"description":"A company record. The field set is not frozen and can grow; treat unknown keys as additive."}},"total":{"type":"integer"},"limit":{"type":"integer"},"offset":{"type":"integer"}},"required":["companies","total","limit","offset"]}}}},"404":{"description":"Error.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"500":{"description":"Error.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/api/headline-stats":{"get":{"operationId":"getHeadlineStats","summary":"Catalog headline counts.","description":"Live counts over active, non-duplicate postings. These are the numbers the site itself displays; prefer them over any figure quoted in prose.","responses":{"200":{"description":"Counts.","content":{"application/json":{"schema":{"type":"object","properties":{"totalJobCount":{"type":"integer"},"companyCount":{"type":"integer"},"countryCount":{"type":"integer"},"newTodayCount":{"type":"integer"}}}}}},"500":{"description":"Error.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/api/health":{"get":{"operationId":"getHealth","summary":"Liveness and dependency status.","description":"Always answers 200, including when a dependency is down. A failed catalog or auth check shows up as `status: \"degraded\"`, not as an error status code.","responses":{"200":{"description":"Service status and the deployed commit.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["ok","degraded"]},"timestamp":{"type":"string","format":"date-time"},"version":{"type":"string"},"release_sha":{"type":"string"},"db":{"type":"string","enum":["connected","timeout","error"],"description":"Absent when no catalog is configured to check."},"auth":{"type":"string","enum":["connected","timeout","error","unconfigured"]}},"required":["status","timestamp","version","release_sha","auth"]}}}}}}}}}