Trajectory · 36 messages, 18 tool calls · report · all reports
How has the shape of startup names changed in 20 years? Classify all Y Combinator companies by name morphology — real word, compound, portmanteau, dropped vowel, -ly, -ify, Latin coinage, invented — and plot the mix by batch year.
tool call · select
SELECT
url,
title,
published_at,
SEM_EXTRACT(
content,
source_type := 'type of source or dataset',
coverage := 'number of Y Combinator companies or batches covered',
fields := 'available company fields, especially name and batch',
access := 'download format or API details'
) AS info
FROM WEB_SEARCH(
'Y Combinator company dataset CSV batch company name all companies',
'YC startup directory API companies batch data',
'GitHub Y Combinator companies CSV batch',
'Kaggle Y Combinator startups dataset batch',
'all YC companies by batch list 2005 2026',
'YC company directory thousands startups batch export',
'Y Combinator startup names dataset analysis naming trends',
'site:ycombinator.com/companies batch S25 W26'
){
"show_preview": false
}result_set_id: r72b04a589be
total_rows: 829
columns[4]: info,published_at,title,url
preview:
sample_rows[3]:
- url: "https://aistory.news/ai-startups-and-companies/yc-ai-startups-2026-why-healthcare-operators-are-rising"
title: "YC AI startups 2026: why healthcare operators are rising"
published_at: "2026-08-23T18:03:14"
info:
source_type: "startup directory page / article reporting on Y Combinator directory statistics and companies list for August 2026 update, featuring listings such as Scale AI, Checkr, and Allia Health across 893 artificial intelligence companies and 5,000+ total companies baseline in Y Combinator's portfolio context context reported by Aistory news category ai startups amp companies, though raw data format itself is not explicitly listed as a downloadable export or API reference file source beyond web listing entries mentioned in text context and reference citations like Bloomberg topics artificial intelligence and OpenAI research instruction following documentation guidelines and US HHS or FDA SaMD references mentioned in body copy rather than structured database file access details specified explicitly in input text source, making source type a news and directory snapshot article commentary rather than a raw dataset download format or API specification details context provided by text source itself under strict reading rules stating none of those database API access properties are fully specified as an API endpoint or CSV download format in the document source itself (source_type: article / startup directory report context summary, coverage: 893 AI companies / 5000+ total companies baseline, fields: company names and descriptions/profiles, access: null). Wait, following instructions strictly to map available fields precisely to schema properties where possible based solely on input text content: coverage is 893 AI companies (and 5000+ total companies baseline), fields mentioned include company names and descriptions (Scale AI, Checkr, Allia Health), access is null, source_type is article / directory snapshot."
coverage: null
fields: null
access: null
- url: "https://ycombinator.com/companies/convexia/jobs"
title: Jobs at Convexia
published_at: null
info: null
- url: "arxiv://2502.14984"
title: "Getting There and Getting In: How Mobility and Sorting Keep Women out of Top Startup Accelerators"
published_at: "2025-07-30T00:00:00"
info: null
notes[4,]{text}:
"The full table is too large to return in one response. This is a preview, not the data — do not draw conclusions from it alone. Query the result set by id: SEM_EXTRACT fields, filter with exact WHERE, aggregate, or page with LIMIT/OFFSET. Even if the preview looks irrelevant, probe the full result set before searching again."
Merged 8 queries (1588 total) → 829 unique results
"Search result set: rbe790af03de (829 rows), reference it as FROM rbe790af03de in follow-up queries"
"WARNING: semantic extraction failed on 256 of 829 rows — extracted values are null on those rows. First error: timed_out after 90.0s"result set r72b04a589be · 829 rows
| url | title | published_at | info |
|---|---|---|---|
| https://aistory.news/ai-startups-and-companies/yc-ai-startups-2026-why-healthcare-operators-are-rising | YC AI startups 2026: why healthcare operators are rising | 2026-08-23T18:03:14 | {"source_type": "startup directory page / article reporting on Y Combinator directory statistics and companies list for August 2026 update, featuring listings … |
| https://ift.tt/CWEZpyc | Founder sues YC over idea in rejected 2018 application | 2026-08-20T08:54:22 | null |
| https://apify.com/automation-lab/ycombinator-scraper | Y Combinator Startups Scraper - Extract YC Company Data · Apify | 2026-03-30T05:54:48 | null |
| https://flowjam.com/blog/yc-startup-directory-the-ultimate-list-guide-2025 | YC Startup Directory 2026: Every Y Combinator Startup | 2025-10-14T16:05:30 | null |
| https://www.ycombinator.com/companies | The YC Startup Directory | null | null |
tool call · select
SELECT
url,
title,
substr(content, 1, 3000) AS content_head
FROM rbe790af03de
WHERE
lower(url) LIKE '%github%'
OR lower(url) LIKE '%kaggle%'
OR lower(url) LIKE '%.csv%'
OR lower(title) LIKE '%dataset%'
OR SEM_MATCH(content, 'provides a downloadable or machine-readable dataset of Y Combinator companies with company name and batch')
LIMIT 100{
"show_preview": false
}result_set_id: r611885b3a4e
total_rows: 100
columns[3]: content_head,title,url
preview:
sample_rows[3,]{url,title,content_head}:
"https://apify.com/automation-lab/ycombinator-scraper",Y Combinator Startups Scraper - Extract YC Company Data · Apify,"startups API[startups.rip ↗](https://startups.rip) Access data on 1,700+ Y Combinato… |
tool call · select
SELECT
url,
title,
SEM_EXTRACT(
content,
repo_or_dataset := 'repository or dataset name',
company_count := 'number of YC company records',
batch_range := 'earliest and latest YC batch covered',
direct_data_url := 'direct raw CSV, JSON, or API URL',
update_date := 'last updated date, YYYY-MM-DD'
, evidence := 'per_field') AS data
FROM WEB_SEARCH(
'GitHub YC companies dataset raw csv batch company name',
'GitHub ycombinator companies json batch_name',
'raw.githubusercontent.com YC startups csv',
'YC companies API batch_name JSON',
'Y Combinator directory GraphQL API companies batch',
'all YC startups CSV 5000',
'Harmonic YC companies dataset CSV',
'yc-oss API companies Y Combinator'
)
WHERE SEM_MATCH(content, 'contains or links to structured YC company records including company names and batch labels'){
"show_preview": false
}result_set_id: rdf2f73b1d80
total_rows: 101
columns[3]: data,title,url
preview:
sample_rows[3]:
- url: "https://apify.com/jungle_synthesizer/y-combinator-scraper"
title: Y Combinator Companies Scraper · Apify
data: null
- url: "https://apify.com/mikolabs/y-combinator-jobs-company-scraper"
title: 🚀 Y Combinator Jobs & Company Scraper · Apify
data: null
- url: "https://github.com/KonstantinMB/exploreyc"
title: "GitHub - KonstantinMB/exploreyc: Explore the Y Combinator portfolio: AI idea validator, success predictor, live hiring board, and email digests. Live at exploreyc.com."
data:
repo_or_dataset: KonstantinMB/exploreyc
company_count: null
batch_range: null
direct_data_url: null
update_date: null
repo_or_dataset_evidence[2]: # KonstantinMB/exploreyc,"To populate it, scrape YC company data from the public Algolia API (no API keys needed):"
company_count_evidence: null
batch_range_evidence: null
direct_data_url_evidence: null
update_date_evidence: null
notes[3,]{text}:
"The full table is too large to return in one response. This is a preview, not the data — do not draw conclusions from it alone. Query the result set by id: SEM_EXTRACT fields, filter with exact WHERE, aggregate, or page with LIMIT/OFFSET. Even if the preview looks irrelevant, probe the full result set before searching again."
Merged 8 queries (1600 total) → 827 unique results
"Search result set: r1a5630484c3 (827 rows), reference it as FROM r1a5630484c3 in follow-up queries"result set rdf2f73b1d80 · 101 rows
| url | title | data |
|---|---|---|
| https://apify.com/jungle_synthesizer/y-combinator-scraper | Y Combinator Companies Scraper · Apify | null |
| https://ycfounderlist.com/blog/how-to-find-yc-companies | How to find YC companies: the complete guide to Y Combinator's company directory | {"repo_or_dataset": null, "company_count": "5,000", "batch_range": "2005 to today", "direct_data_url": null, "update_date": "2026-06", "repo_or_dataset_evidenc… |
| https://apify.com/scrapesage/ycombinator-scraper | Y Combinator Scraper - Companies, Founders & Jobs · Apify | null |
| https://apify.com/automation-lab/ycombinator-scraper | Y Combinator Startups Scraper - Extract YC Company Data · Apify | {"repo_or_dataset": "Y Combinator Startups Scraper", "company_count": null, "batch_range": null, "direct_data_url": null, "update_date": null, "repo_or_dataset… |
| https://prodsens.live/2026/05/22/track-yc-demo-day-companies-in-real-time-with-code | Track YC Demo Day Companies in Real Time (with code) | {"repo_or_dataset": "YC Companies Directory", "company_count": null, "batch_range": null, "direct_data_url": null, "update_date": null, "repo_or_dataset_eviden… |
tool call · select
SELECT
url,
title,
substr(content, 1, 10000) AS content_excerpt
FROM WEB_FETCH(
'https://github.com/KonstantinMB/exploreyc',
'https://raw.githubusercontent.com/KonstantinMB/exploreyc/main/README.md'
){
"show_preview": false
}result_set_id: refaa333d564
total_rows: 1
columns[3]: content_excerpt,title,url
rows[1,]{url,title,content_excerpt}:
"https://github.com/KonstantinMB/exploreyc","GitHub - KonstantinMB/exploreyc: Explore the Y Combinator portfolio: AI idea validator, success predictor, live hiring board, and email digests. Live at exploreyc.com.","# KonstantinMB/exploreyc\n\n## Folders and files\n\nFolders and files\n| Name | | Name | Last commit message | Last commit date |\n|---|---|---|---|---|\n| ## Latest commit ## History | | | | |\n| | | | | |\n| | | | | |\n| | | | | |\n| | | | | |\n| | | | | |\n| | | | | |\n| | | | | |\n| | | | | |\n| | | | | |\n| | | | | |\n| | | | | |\n| | | | | |\n| | | | | |\n| | | | | |\n| | | | | |\n| | | | | |\n| | | | | |\n| | | | | |\n| | | | | |\n| | | | | |\n| | | | | |\n| | | | | |\n| | | | | |\n\n---\n\n## Why this exists\n\nThere's no good way to slice the YC portfolio if you're not a YC partner with database access. Algolia search is fine for finding one company; it's bad for \"show me every Fintech company hiring engineers in Europe that raised in the last six months.\" So I built one. It started as a CLI scraper and grew into a deployed web app.\n\nThis was a closed-source project until the Product Hunt launch made it clear there's an audience of YC-curious builders who want different cuts of this data than I do. So now you can build them.\n\n## Features\n\n- **Company explorer** — search, filter, and sort the full YC portfolio\n- **Idea validator** — paste a startup idea, see the most semantically-similar YC companies (OpenAI embeddings)\n- **Gamified success predictor** — score an idea + team profile against historical YC patterns\n- **Live hiring board** — currently-open YC roles, filterable by company / role / location\n- **Email digests** — subscribe to weekly updates on new batches\n- **Maps & analytics** — geographic distribution, batch trends, industry breakdowns\n- **Admin dashboard** — scrape jobs, subscription management, manual tools (auth-gated)\n\n> Try it now: **[exploreyc.com](https://exploreyc.com)** — no signup required.\n\n## Tech stack\n\n| Layer | What |\n|---|---|\n| Frontend | React 19, Vite, TypeScript, Tailwind, TanStack Query, Radix UI, Recharts, React Leaflet, ReactFlow |\n| Backend | FastAPI (Python 3.11), WebSocket support |\n| Database | SQLite (local) → PostgreSQL via Supabase (prod). Selected at runtime by `backend/database_factory.py` |\n| Hosting | Vercel (frontend + cron) + Render (backend) |\n| LLM / enrichment | OpenAI, Perplexity, CoreSignal |\n| Email | Resend |\n\n---\n\n## Quick start\n\nYou need Python 3.11+ and Node 20+.\n\n```\n git clone https://github.com/KonstantinMB/exploreyc.git cd exploreyc # Backend cd backend python3 -m venv venv source venv/bin/activate pip install -r requirements.txt cp ../.env.example ../.env # fill in the keys you have python3 -m uvicorn main:app --reload # → http://localhost:8000 # Frontend (new shell) cd ../frontend npm install npm run dev # → http://localhost:5173\n```\n\nThe Vite dev server proxies `/api` and `/ws` to `localhost:8000`, so the frontend talks to your local backend automatically.\n\n**Minimum viable local setup:** leave `DATABASE_URL` unset (falls back to SQLite) and provide just an `OPENAI_API_KEY` for the idea-validator features. Every other integration degrades gracefully if its key is missing — see [`.env.example`](/KonstantinMB/exploreyc/blob/master/.env.example) for the full list.\n\n### Seeding local data\n\nThe local SQLite database starts empty. To populate it, scrape YC company data from the public Algolia API (no API keys needed):\n\n```\n # With the backend running, seed the last 3 batches (~500+ companies) curl -X POST http://localhost:8000/api/scrape \\ -H \" Content-Type: application/json \" \\ -d ' {\"batch\": [\"Winter 2025\", \"Summer 2024\", \"Winter 2024\"], \"hits_per_page\": 1000, \"max_pages\": 20} ' # Check progress (replace 1 with the job_id returned above) curl http://localhost:8000/api/scrape/status/1 # Verify data loaded curl http://localhost:8000/api/stats\n```\n\nOnce the scrape completes (usually under a minute), refresh the frontend to see charts and data populate. To scrape all batches, omit the `batch` filter — but this takes longer and hits the 3-requests-per-hour rate limit.\n\n## Project layout\n\n```\n backend/ FastAPI app — most endpoints in main.py, services as siblings frontend/ React SPA, React Router api/cron/ Vercel cron handlers (call back into the backend) supabase/ Migrations, applied in filename order scripts/ Maintenance helpers (embeddings backfill, funding enrichment) .github/ Issue templates, CI, scheduled GitHub Actions cron\n```\n\nDetailed architecture notes are in [`CLAUDE.md`](/KonstantinMB/exploreyc/blob/master/CLAUDE.md) — written for AI assistants, useful for humans too.\n\n---\n\n## Contributing\n\nPRs and ideas welcome. This is a one-person project and I'm not precious about it. **The goal is to grow this into the explorer the YC-curious community actually wants — your version, not just mine.**\n\n### I have an idea but I don't write code\n\nOpen a new issue using the [`💡 Idea` template](https://github.com/KonstantinMB/exploreyc/issues/new/choose). Describe the user, the problem, and what you wish existed. Don't worry about feasibility — that's my problem. Half-baked is fine. Drawings, screenshots, links to similar features elsewhere — all welcome.\n\nIf you'd rather send it privately: `konstantin.borimechkov14@gmail.com`.\n\n### I want to ship something\n\n1. Skim the [open issues](https://github.com/KonstantinMB/exploreyc/issues) and the Open ideas section below for inspiration.\n2. **Open an issue first** if the change is non-trivial — saves you (and me) the cost of building something that won't merge.\n3. Fork → branch (`feature/short-name` or `fix/short-name`) → PR against `master`.\n4. Keep PRs focused. One feature or one fix per PR. Smaller diffs ship faster.\n5. If your change touches the API surface, env vars, or migrations, update `.env.example` + the relevant section of this README (or `CLAUDE.md`) in the same PR.\nI try to respond to issues within 48h and review PRs within a week. If I'm slow, ping me — it's not personal.\n\n### What I'll merge enthusiastically\n\n- New filters, views, or analytics on existing data\n- New data integrations that fit the mission (other accelerators, public funding sources, etc.)\n- Performance improvements (`backend/main.py` is a 3000-line monolith on purpose, not by accident — but most of it is ripe for tightening)\n- Bug fixes with a test or reproduction\n- Documentation / onboarding improvements\n- A frontend test suite (there is none yet — first contributor gets eternal gratitude)\n\n### What I'll push back on\n\n- Breaking the \"no required signup\" model — ExploreYC works for unauthenticated visitors and I want to keep it that way for the core features\n- Adding heavy dependencies for marginal gains\n- Stylistic refactors without a behavioral reason\n- Anything that puts user data, scraped data, or third-party API keys at risk\n- Forking the data model in ways that complicate the SQLite-or-Postgres factory pattern\nIf you're unsure, open an issue and ask before building. I'd rather have the conversation than reject a PR you spent a weekend on.\n\n### Local dev workflow\n\n- Backend autoreloads with `uvicorn main:app --reload`\n- Frontend hot-reloads with `npm run dev`\n- Type-check the frontend before pushing: `cd frontend && npm run build`\n- Run backend tests: `cd backend && python -m pytest`\n- Migrations live in `supabase/migrations/` and are applied in filename order. When adding one, use the next timestamp prefix.\n\n### Code conventions\n\nThere's no enforced lint config — match surrounding code style.\n\n- **Backend:** PEP 8-ish, type hints where they help, log don't print, raise `HTTPException` for user-facing errors, group related endpoints in `main.py` with a `# ===== SECTION =====` comment\n- **Frontend:** function components, hooks, Tailwind for styling, TanStack Query for any data that comes from the backend, Radix primitives over custom modals/popovers\n- **Secrets:** the only env file in the repo is `.env.example`. Everything sensitive goes in your local `.env`, which is gitignored.\n- **Rate-limited endpoints:** if you add an endpoint that calls a paid API (OpenAI / Perplexity / CoreSignal), wire it up with `_enforce_rate_limit(...)` — see `backend/main.py` near line 90 for the pattern.\n\n---\n\n## Open ideas\n\nThings I've been thinking about but haven't built. Pick one, open an issue if you have questions, ship it. None of these are claimed.\n\n- **Batch comparison view** — side-by-side stats for any two YC batches (size, vertical mix, hiring %, top hires, etc.)\n- **Founder profile pages** — aggregate companies founded by the same person across batches\n- **CLI mode** — bring back a packaged CLI for people who want JSON/CSV exports without running the full web app\n- **Non-YC accelerator support** — Techstars, 500, Antler, EF. Plug-in architecture for the scraper\n- **Better embedding model for the idea validator** — currently `text-embedding-3-small`; experiment with newer or open-source models\n- **Notion / Slack / Discord integrations** — push the email digest to other channels\n- **Public read-only API** — rate-limited dataset endpoints, no key required\n- **Frontend test suite** — Vitest + React Testing Library. Doesn't have to cover everything; a baseline that runs in CI would be huge\n- **Diff view on YC batches** — \"what changed between Spring and Summer 2026\"\n- **Internationalization** — site is English-only; YC's reach isn't\n- **Self-hosted deploy guide** — Docker Compose recipe for people who don't want Vercel + Render\nIf your idea isn't on the list, that doesn't mean no. Open an issue and let's talk.\n\n---\n\n## Testing\n\n```\n cd backend python -m pytest test_email_digest.py python -m pytest test_pagination.py\n```\n\nBackend tests are integration-flavored and may hit real services if the relevant keys are set. There's no frontend test suite yet (see Open ideas).\n\n## Deploying your own instance\n\nThe hosted version uses Vercel (frontend + Vercel cron + rewrites that proxy `/api/*` to `api.exploreyc.com`) and Render (backend). The repo is wired for that, but nothing prevents another layout.\n\nSee [`DEPLOYMENT.md`](/KonstantinMB/exploreyc/blob/master/DEPLOYMENT.md) for the step-by-step.\n\n## Security\n\nFound a vulnerability? See [`SECURITY.md`](/KonstantinMB/exploreyc/blob/"
notes[2,]{text}:
"WARNING: 1 of 2 URLs failed and were skipped: https://raw.githubusercontent.com/KonstantinMB/exploreyc/main/README.md"
"Fetch result set: rce8bfb7cce6 (1 rows), reference it as FROM rce8bfb7cce6 in follow-up queries"result set refaa333d564 · 1 rows
| url | title | content_excerpt |
|---|---|---|
| https://github.com/KonstantinMB/exploreyc | GitHub - KonstantinMB/exploreyc: Explore the Y Combinator portfolio: AI idea validator, success predictor, live hiring board, and email digests. Live at explor… | # KonstantinMB/exploreyc ## Folders and files Folders and files | Name | | Name | Last commit message | Last commit date | |---|---|---|---|---| | ## Latest … |
tool call · select
SELECT
url,
title,
substr(content, 1, 20000) AS content_excerpt
FROM WEB_FETCH(
'https://raw.githubusercontent.com/KonstantinMB/exploreyc/master/backend/scraper.py',
'https://raw.githubusercontent.com/KonstantinMB/exploreyc/master/backend/main.py',
'https://github.com/KonstantinMB/exploreyc/blob/master/backend/scraper.py'
){
"show_preview": false
}result_set_id: rfa7b566958a
total_rows: 1
columns[3]: content_excerpt,title,url
rows[1,]{url,title,content_excerpt}:
"https://raw.githubusercontent.com/KonstantinMB/exploreyc/master/backend/main.py",null,"\"\"\"\nFastAPI backend for YC Company Scraper\n\"\"\"\n\n# Load environment variables from .env file (for local development)\nfrom dotenv import load_dotenv\nload_dotenv()\n\nfrom fastapi import FastAPI, HTTPException, WebSocket, WebSocketDisconnect, BackgroundTasks, Request, Depends, Header\nfrom fastapi.middleware.cors import CORSMiddleware\nfrom fastapi.responses import StreamingResponse, FileResponse, JSONResponse, Response, HTMLResponse\nfrom pydantic import BaseModel\nfrom typing import Optional, List\nimport asyncio\nimport csv\nimport hashlib\nimport io\nimport json\nimport logging\nimport os\nimport secrets\nimport requests\nfrom urllib.parse import urlparse\nfrom datetime import datetime, timedelta, timezone\nfrom collections import defaultdict\nfrom time import time\n\nfrom database_factory import get_database\nfrom scraper_service import ScraperService\nfrom email_service import EmailService\nfrom embedding_service import get_embedding_service\nfrom idea_filter import get_search_text_for_embedding\nfrom og_image_generator import get_og_image_generator\nfrom company_cache import CompanyCache\nfrom coresignal_service import coresignal_service\nfrom hiring_service import get_hiring_service\nfrom gamification_scoring import GamificationScorer\nfrom perplexity_service import get_perplexity_service\nfrom hero_service import build_verdict\nimport ratelimit\n\nlogging.basicConfig(level=logging.INFO)\nlogger = logging.getLogger(__name__)\n\n# Hide the internal API's docs/OpenAPI in production so admin/cron routes aren't published.\n# The public API keeps its own docs at /api/v1/docs.\n_IS_PROD = os.environ.get(\"ENV\", \"\").lower() == \"production\"\napp = FastAPI(\n title=\"YC Company Scraper API\",\n version=\"1.0.0\",\n docs_url=None if _IS_PROD else \"/docs\",\n redoc_url=None if _IS_PROD else \"/redoc\",\n openapi_url=None if _IS_PROD else \"/openapi.json\",\n)\n\n# Static mount for re-hosted founder avatars (spec §5.4, dev path).\nfrom fastapi.staticfiles import StaticFiles\n_STATIC_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), \"static\")\nos.makedirs(os.path.join(_STATIC_DIR, \"avatars\"), exist_ok=True)\napp.mount(\"/static\", StaticFiles(directory=_STATIC_DIR), name=\"static\")\n\n\n# Rate Limiter for Research Endpoint\nclass ResearchRateLimiter:\n \"\"\"Simple rate limiter for research requests (5 requests per minute per IP)\"\"\"\n def __init__(self, max_requests: int = 5, window_seconds: int = 60):\n self.max_requests = max_requests\n self.window_seconds = window_seconds\n self.requests: dict[str, list[float]] = defaultdict(list)\n\n def is_allowed(self, client_ip: str) -> bool:\n \"\"\"Check if request is allowed for this client IP\"\"\"\n now = time()\n # Remove old requests outside the window\n self.requests[client_ip] = [\n req_time for req_time in self.requests[client_ip]\n if now - req_time < self.window_seconds\n ]\n\n # Check if within limit\n if len(self.requests[client_ip]) >= self.max_requests:\n return False\n\n # Add new request\n self.requests[client_ip].append(now)\n return True\n\n def get_reset_time(self, client_ip: str) -> int:\n \"\"\"Get seconds until rate limit resets\"\"\"\n if not self.requests[client_ip]:\n return 0\n oldest_request = min(self.requests[client_ip])\n reset_time = int(oldest_request + self.window_seconds - time())\n return max(0, reset_time)\n\n\nresearch_rate_limiter = ResearchRateLimiter(max_requests=5, window_seconds=60)\n\n# Additional IP-based limiters for expensive / abuse-prone endpoints.\n# Each one wraps an LLM call, an email send, or a long-running background job —\n# without these an unauthenticated `for` loop can drain provider balances.\nvalidate_idea_limiter = ResearchRateLimiter(max_requests=5, window_seconds=60)\ngamified_predict_limiter = ResearchRateLimiter(max_requests=5, window_seconds=60)\nresearch_query_limiter = ResearchRateLimiter(max_requests=5, window_seconds=60)\nsubscribe_limiter = ResearchRateLimiter(max_requests=3, window_seconds=60)\nscrape_limiter = ResearchRateLimiter(max_requests=3, window_seconds=3600)\ndev_auth_limiter = ResearchRateLimiter(max_requests=10, window_seconds=3600) # signup/login per IP\n\n\ndef _enforce_rate_limit(limiter: ResearchRateLimiter, request_obj: Request, label: str) -> None:\n \"\"\"Raise HTTP 429 if the caller's IP has exceeded `limiter`.\"\"\"\n client_ip = request_obj.client.host if request_obj.client else \"unknown\"\n if not limiter.is_allowed(client_ip):\n reset = limiter.get_reset_time(client_ip)\n raise HTTPException(\n status_code=429,\n detail=f\"Rate limit exceeded for {label}. Try again in {reset} seconds.\",\n )\n\n\ndef _log_startup_warnings():\n \"\"\"Log warnings for missing config at startup\"\"\"\n if not os.environ.get(\"RESEND_API_KEY\"):\n logger.warning(\"RESEND_API_KEY not set - email subscriptions will save but verification emails will NOT be sent\")\n\n\ndef _safe_string(value) -> str:\n \"\"\"Safely convert any value to string, handling lists and None\"\"\"\n if isinstance(value, list):\n value = value[0] if value else \"\"\n if value is None:\n return \"\"\n return str(value)\n\n\n# CORS - allow localhost + production frontend (including www variant)\n_cors_origins = [\"http://localhost:5173\", \"http://localhost:3000\", \"https://www.workatastartup.com\"]\nif os.environ.get(\"FRONTEND_URL\"):\n url = os.environ[\"FRONTEND_URL\"].rstrip(\"/\")\n if url not in _cors_origins:\n _cors_origins.append(url)\n # Also allow www variant (e.g. www.exploreyc.com when FRONTEND_URL is exploreyc.com)\n if url.startswith(\"https://\"):\n domain = url.replace(\"https://\", \"\")\n if not domain.startswith(\"www.\"):\n www_url = f\"https://www.{domain}\"\n if www_url not in _cors_origins:\n _cors_origins.append(www_url)\n elif url.startswith(\"http://\"):\n domain = url.replace(\"http://\", \"\")\n if not domain.startswith(\"www.\"):\n www_url = f\"http://www.{domain}\"\n if www_url not in _cors_origins:\n _cors_origins.append(www_url)\nif os.environ.get(\"VERCEL_URL\"):\n u = f\"https://{os.environ['VERCEL_URL']}\"\n if u not in _cors_origins:\n _cors_origins.append(u)\n\napp.add_middleware(\n CORSMiddleware,\n allow_origins=_cors_origins,\n allow_credentials=True,\n allow_methods=[\"GET\", \"POST\", \"PUT\", \"DELETE\", \"OPTIONS\", \"PATCH\"],\n allow_headers=[\"Content-Type\", \"Authorization\", \"Accept\", \"Origin\", \"X-Requested-With\"],\n)\n\n# Initialize database, scraper service, email service, and company cache\ndb = get_database()\nscraper = ScraperService(db)\nfrom a16z_scraper_service import A16ZScraperService\na16z_scraper = A16ZScraperService(db)\nemail_service = EmailService()\ncompany_cache = CompanyCache()\n\n\ndef get_last_2_years_batches() -> List[str]:\n \"\"\"Get YC batch names for the last 2 years (e.g., Summer 2024, Winter 2024, ...)\"\"\"\n from datetime import date\n current_year = date.today().year\n seasons = [\"Winter\", \"Spring\", \"Summer\", \"Fall\"]\n batches = []\n for year in [current_year - 2, current_year - 1, current_year]:\n for season in seasons:\n batches.append(f\"{season} {year}\")\n return batches\n\n\n@app.on_event(\"startup\")\nasync def startup_check_database():\n \"\"\"Check database status on startup and load company cache\"\"\"\n _log_startup_warnings()\n company_cache.load(db)\n stats = company_cache.get_stats()\n logger.info(f\"Startup: Company cache loaded with {stats['total_companies']:,} companies (hiring: {stats['hiring']:,}, batches: {len(stats['by_batch'])})\")\n\n # Load hiring board cache\n hiring_service = get_hiring_service()\n hiring_service.cache.load(db)\n hiring_stats = hiring_service.get_hiring_stats()\n logger.info(f\"Startup: Hiring cache loaded with {hiring_stats.get('totalJobs', 0):,} jobs from {hiring_stats.get('hiringCompanies', 0):,} companies\")\n\n # Note: Removed automatic scraping on startup since we now have a complete database\n # Use the /api/scrape endpoint or run scrape_all_by_batch.py to update companies\n\n# WebSocket connection manager\nclass ConnectionManager:\n def __init__(self):\n self.active_connections: List[WebSocket] = []\n\n async def connect(self, websocket: WebSocket):\n await websocket.accept()\n self.active_connections.append(websocket)\n\n def disconnect(self, websocket: WebSocket):\n self.active_connections.remove(websocket)\n\n async def broadcast(self, message: dict):\n for connection in self.active_connections:\n try:\n await connection.send_json(message)\n except:\n pass\n\nmanager = ConnectionManager()\n\n\n# Admin Session Management\nadmin_sessions = {} # {token: {username: str, expires: datetime}}\n\ndef create_admin_session(username: str) -> str:\n \"\"\"Create a new admin session token\"\"\"\n token = secrets.token_urlsafe(32)\n admin_sessions[token] = {\n \"username\": username,\n \"expires\": datetime.utcnow() + timedelta(hours=24)\n }\n return token\n\ndef verify_admin_session(authorization: Optional[str] = Header(None)) -> dict:\n \"\"\"Verify admin session token from Authorization header\"\"\"\n if not authorization:\n raise HTTPException(status_code=401, detail=\"Missing authorization header\")\n\n if not authorization.startswith(\"Bearer \"):\n raise HTTPException(status_code=401, detail=\"Invalid authorization header format\")\n\n token = authorization.replace(\"Bearer \", \"\")\n\n session = admin_sessions.get(token)\n if not session:\n raise HTTPException(status_code=401, detail=\"Invalid or expired session\")\n\n if session[\"expires\"] < datetime.utcnow():\n del admin_sessions[token]\n raise HTTPException(status_code=401, detail=\"Session expired\")\n\n return session\n\n\n# Pydantic models\nclass AdminLoginRequest(BaseModel):\n username: str\n password: str\n\n\n\nclass ScrapeRequest(BaseModel):\n query: str = \"\"\n batch: Optional[List[str]] = None\n industry: Optional[List[str]] = None\n region: Optional[List[str]] = None\n is_hiring: Optional[bool] = None\n top_company: Optional[bool] = None\n nonprofit: Optional[bool] = None\n hits_per_page: int = 1000\n max_pages: int = 10\n source: str = \"yc\" # 'yc' (Algolia) or 'a16z' (portfolio page)\n\n\nclass CompanyFilter(BaseModel):\n limit: int = 100\n offset: int = 0\n batch: Optional[str] = None\n is_hiring: Optional[bool] = None\n industry: Optional[str] = None\n country: Optional[str] = None\n search: Optional[str] = None\n top_company: Optional[bool] = None\n source: Optional[str] = None # None -> YC only, 'all' -> every source, or a source key\n merged: bool = False # collapse same-domain rows across sources into one card\n has_logo: bool = False # only companies with a real, renderable logo\n\n\n# Background task storage\nactive_jobs = {}\n\n\n# Routes\n@app.get(\"/\")\nasync def root():\n return {\"message\": \"YC Company Scraper API\", \"version\": \"1.0.0\"}\n\n\n@app.get(\"/api/health\")\nasync def health():\n \"\"\"Health check endpoint\"\"\"\n return {\"status\": \"healthy\", \"timestamp\": datetime.utcnow().isoformat()}\n\n\n# Admin Authentication Endpoints\n@app.post(\"/api/admin/login\")\nasync def admin_login(request: AdminLoginRequest):\n \"\"\"Admin login endpoint\"\"\"\n admin_username = os.environ.get(\"ADMIN_USERNAME\")\n admin_password = os.environ.get(\"ADMIN_PASSWORD\")\n\n if not admin_username or not admin_password:\n raise HTTPException(status_code=500, detail=\"Admin credentials not configured\")\n\n if request.username != admin_username or request.password != admin_password:\n raise HTTPException(status_code=401, detail=\"Invalid credentials\")\n\n token = create_admin_session(request.username)\n\n return {\n \"token\": token,\n \"username\": request.username,\n \"expires_in\": 86400 # 24 hours in seconds\n }\n\n\n@app.post(\"/api/admin/logout\")\nasync def admin_logout(session: dict = Depends(verify_admin_session), authorization: str = Header(None)):\n \"\"\"Admin logout endpoint\"\"\"\n token = authorization.replace(\"Bearer \", \"\")\n if token in admin_sessions:\n del admin_sessions[token]\n\n return {\"message\": \"Logged out successfully\"}\n\n\n@app.get(\"/api/admin/session\")\nasync def admin_session_check(session: dict = Depends(verify_admin_session)):\n \"\"\"Check if admin session is valid\"\"\"\n return {\n \"username\": session[\"username\"],\n \"expires\": session[\"expires\"].isoformat()\n }\n\n\n@app.get(\"/api/admin/email-config\")\nasync def get_email_config(session: dict = Depends(verify_admin_session)):\n \"\"\"Get email configuration status\"\"\"\n from email_service import EmailService\n\n email_service = EmailService()\n\n return {\n \"resend_configured\": bool(email_service.api_key and not email_service.api_key.startswith(\"re_your\")),\n \"from_email\": email_service.from_email,\n \"cron_secret_configured\": bool(os.environ.get(\"CRON_SECRET\"))\n }\n\n\n@app.post(\"/api/admin/test-verification-email\")\nasync def send_test_verification_email(\n request: dict,\n session: dict = Depends(verify_admin_session)\n):\n \"\"\"Send a test verification email\"\"\"\n from email_service import EmailService\n import secrets\n\n email = request.get(\"email\")\n if not email:\n raise HTTPException(status_code=400, detail=\"Email address required\")\n\n email_service = EmailService()\n\n if not email_service.api_key or email_service.api_key.startswith(\"re_your\"):\n raise HTTPException(\n status_code=500,\n detail=\"RESEND_API_KEY not configured. Set it in environment variables.\"\n )\n\n # Generate a test token\n test_token = secrets.token_urlsafe(32)\n\n # Send the email\n success = email_service.send_verification_email(email, test_token)\n\n if not success:\n raise HTTPException(status_code=500, detail=\"Failed to send email. Check logs for details.\")\n\n return {\n \"success\": True,\n \"message\": f\"Test verification email sent to {email}\",\n \"email\": email\n }\n\n\n@app.get(\"/api/admin/enrichment/stats\")\nasync def get_enrichment_stats(session: dict = Depends(verify_admin_session)):\n \"\"\"Get enrichment statistics and progress\"\"\"\n try:\n with db.get_connection() as conn:\n cursor = conn.cursor()\n\n # Total companies (YC only — Coresignal enrichment is YC-scoped)\n cursor.execute(\"SELECT COUNT(*) FROM companies WHERE source = 'yc'\")\n total_companies = cursor.fetchone()[0]\n\n # Companies enriched (have coresignal data)\n cursor.execute(\"SELECT COUNT(*) FROM companies WHERE coresignal_last_updated IS NOT NULL\")\n enriched_count = cursor.fetchone()[0]\n\n # Companies with funding data\n cursor.execute(\"SELECT COUNT(*) FROM companies WHERE funding_total_usd IS NOT NULL\")\n with_funding_amount = cursor.fetchone()[0]\n\n # Companies with funding rounds (but maybe no amount)\n cursor.execute(\"SELECT COUNT(*) FROM companies WHERE funding_last_round_name IS NOT NULL\")\n with_funding_rounds = cursor.fetchone()[0]\n\n # Recent enrichments (last 24 hours)\n # PostgreSQL syntax\n cursor.execute(\"\"\"\n SELECT COUNT(*) FROM companies\n WHERE coresignal_last_updated IS NOT NULL\n AND coresignal_last_updated > NOW() - INTERVAL '1 day'\n \"\"\")\n recent_enrichments = cursor.fetchone()[0]\n\n # Top enriched companies\n cursor.execute(\"\"\"\n SELECT id, name, batch, funding_total_usd, funding_last_round_name, funding_last_round_date, investors_count\n FROM companies\n WHERE coresignal_last_updated IS NOT NULL\n ORDER BY funding_total_usd DESC NULLS LAST\n LIMIT 10\n \"\"\")\n top_enriched = []\n for row in cursor.fetchall():\n top_enriched.append({\n \"id\": row[0],\n \"name\": row[1],\n \"batch\": row[2],\n \"funding_total_usd\": row[3],\n \"funding_last_round_name\": row[4],\n \"funding_last_round_date\": row[5],\n \"investors_count\": row[6]\n })\n\n return {\n \"total_companies\": total_companies,\n \"enriched_count\": enriched_count,\n \"unenriched_count\": total_companies - enriched_count,\n \"with_funding_amount\": with_funding_amount,\n \"with_funding_rounds\": with_funding_rounds,\n \"recent_enrichments_24h\": recent_enrichments,\n \"enrichment_percentage\": round((enriched_count / total_companies * 100), 2) if total_companies > 0 else 0,\n \"funding_data_percentage\": round((with_funding_amount / enriched_count * 100), 2) if enriched_count > 0 else 0,\n \"top_enriched\": top_enriched,\n \"coresignal_enabled\": coresignal_service.enabled\n }\n\n except Exception as e:\n logger.error(f\"Error getting enrichment stats: {e}\")\n raise HTTPException(status_code=500, detail=str(e))\n\n\n@app.post(\"/api/scrape\")\nasync def start_scrape(request: ScrapeRequest, background_tasks: BackgroundTasks, request_obj: Request):\n \"\"\"Start a new scraping job\"\"\"\n _enforce_rate_limit(scrape_limiter, request_obj, \"scrape jobs\")\n\n # Create job in database\n job_id = db.create_scrape_job({\n 'source': request.source,\n 'query': request.query,\n 'batch': request.batch,\n 'industry': request.industry,\n 'region': request.region,\n 'is_hiring': request.is_hiring,\n 'top_company': request.top_company,\n 'nonprofit': request.nonprofit,\n 'hits_per_page': request.hits_per_page,\n 'max_pages': request.max_pages,\n })\n\n # Progress callback for WebSocket updates\n async def progress_callback(data):\n await manager.broadcast(data)\n\n # Start scraping in background\n async def run_scrape():\n try:\n if request.source == \"a16z\":\n total = await a16z_scraper.scrape_companies(\n job_id=job_id,\n progress_callback=progress_callback,\n )\n else:\n total = await scraper.scrape_companies(\n job_id=job_id,\n query=request.query,\n batch=request.batch,\n industry=request.industry,\n region=request.region,\n is_hiring=request.is_hiring,\n top_company=request.top_company,\n nonprofit=request.nonprofit,\n hits_per_page=request.hits_per_page,\n max_pages=request.max_pages,\n progress_callback=progress_callback\n )\n active_jobs[job_id] = {'status': 'completed', 'total': total}\n except Exception as e:\n active_jobs[job_id] = {'status': 'failed', 'error': str(e)}\n finally:\n # Refresh in-memory cache after scrape completes (success or failure)\n company_cache.refresh(db)\n\n # Schedule background task\n asyncio.create_task(run_scrape())\n active_jobs[job_id] = {'status': 'running'}\n\n return {\n \"job_id\": job_id,\n \"status\": \"started\",\n \"message\": \"Scraping job started\"\n }\n\n\n@app.get(\"/api/scrape/status/{job_id}\")\nasync def get_scrape_status(job_id: int):\n \"\"\"Get the status of a scraping job\"\"\"\n job = db.get_scrape_job(job_id)\n\n if not job:\n raise HTTPException(status_code=404, detail=\"Job not found\")\n\n return {\n \"job_id\": job_id,\n \"status\": job['status'],\n \"total_scraped\": job['total_scraped'],\n \"current_page\": job['current_page'],\n \"error\": job['erro"
notes[2,]{text}:
"WARNING: 2 of 3 URLs failed and were skipped: https://raw.githubusercontent.com/KonstantinMB/exploreyc/master/backend/scraper.py, https://github.com/KonstantinMB/exploreyc/blob/master/backend/scraper.py"
"Fetch result set: re05aff784d1 (1 rows), reference it as FROM re05aff784d1 in follow-up queries"result set rfa7b566958a · 1 rows
| url | title | content_excerpt |
|---|---|---|
| https://raw.githubusercontent.com/KonstantinMB/exploreyc/master/backend/main.py | null | """ FastAPI backend for YC Company Scraper """ # Load environment variables from .env file (for local development) from dotenv import load_dotenv load_dotenv(… |
tool call · select
SELECT
url,
title,
substr(content, 1, 16000) AS content_excerpt
FROM WEB_FETCH(
'https://raw.githubusercontent.com/KonstantinMB/exploreyc/master/backend/scraper_service.py',
'https://api.exploreyc.com/api/stats',
'https://api.exploreyc.com/api/companies?limit=10&offset=0',
'https://api.exploreyc.com/api/v1/companies?limit=10&offset=0'
){
"show_preview": false
}result_set_id: re25a0d539fd
total_rows: 2
columns[3]: content_excerpt,title,url
rows[2,]{url,title,content_excerpt}:
"https://raw.githubusercontent.com/KonstantinMB/exploreyc/master/backend/scraper_service.py",null,"\"\"\"\nScraper service for background job processing\n\"\"\"\n\nimport asyncio\nimport requests\nimport json\nfrom typing import Dict, Optional, List, Callable\nfrom urllib.parse import urlencode\nfrom database import Database\nfrom enhanced_geocoding import EXTENDED_LOCATION_COORDS\n\n\nclass ScraperService:\n \"\"\"Service to handle background scraping jobs\"\"\"\n\n ALGOLIA_URL = \"https://45bwzj1sgc-dsn.algolia.net/1/indexes/*/queries\"\n APP_ID = \"45BWZJ1SGC\"\n API_KEY = \"NzllNTY5MzJiZGM2OTY2ZTQwMDEzOTNhYWZiZGRjODlhYzVkNjBmOGRjNzJiMWM4ZTU0ZDlhYTZjOTJiMjlhMWFuYWx5dGljc1RhZ3M9eWNkYyZyZXN0cmljdEluZGljZXM9WUNDb21wYW55X3Byb2R1Y3Rpb24lMkNZQ0NvbXBhbnlfQnlfTGF1bmNoX0RhdGVfcHJvZHVjdGlvbiZ0YWdGaWx0ZXJzPSU1QiUyMnljZGNfcHVibGljJTIyJTVE\"\n\n # Use enhanced geocoding database with 150+ cities worldwide\n LOCATION_COORDS = EXTENDED_LOCATION_COORDS\n\n def __init__(self, db: Database):\n self.db = db\n self.session = requests.Session()\n self.session.headers.update({\n 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36',\n 'Accept': 'application/json',\n 'Content-Type': 'application/x-www-form-urlencoded',\n 'Origin': 'https://www.ycombinator.com',\n 'Referer': 'https://www.ycombinator.com/'\n })\n\n def _build_facet_filters(self, filters: Dict[str, List[str]]) -> List:\n \"\"\"Build facet filters for Algolia query\"\"\"\n facet_filters = []\n for key, values in filters.items():\n if values:\n facet_filters.append([f\"{key}:{value}\" for value in values])\n return facet_filters\n\n def _geocode_location(self, location: str) -> Optional[Dict]:\n \"\"\"Simple geocoding using predefined coordinates\"\"\"\n if not location:\n return None\n\n location = location.strip()\n\n # Try exact match\n if location in self.LOCATION_COORDS:\n return self.LOCATION_COORDS[location]\n\n # Try partial match\n for city, coords in self.LOCATION_COORDS.items():\n if city.lower() in location.lower():\n return coords\n\n return None\n\n def _enrich_company_data(self, company: Dict) -> Dict:\n \"\"\"Enrich company data with geocoding\"\"\"\n location = company.get('all_locations', '')\n\n if location:\n coords = self._geocode_location(location)\n if coords:\n company['latitude'] = coords['lat']\n company['longitude'] = coords['lng']\n company['country'] = coords['country']\n\n return company\n\n async def scrape_companies(self,\n job_id: int,\n query: str = \"\",\n batch: Optional[List[str]] = None,\n industry: Optional[List[str]] = None,\n region: Optional[List[str]] = None,\n is_hiring: Optional[bool] = None,\n top_company: Optional[bool] = None,\n nonprofit: Optional[bool] = None,\n hits_per_page: int = 1000,\n max_pages: int = 10,\n progress_callback: Optional[Callable] = None) -> int:\n \"\"\"\n Scrape YC companies with filters (async)\n \"\"\"\n\n try:\n # Build facet filters\n filters = {}\n if batch:\n filters['batch'] = batch\n if industry:\n filters['industries'] = industry\n if region:\n filters['regions'] = region\n if is_hiring is not None:\n filters['isHiring'] = [str(is_hiring).lower()]\n if top_company is not None:\n filters['top_company'] = [str(top_company).lower()]\n if nonprofit is not None:\n filters['nonprofit'] = [str(nonprofit).lower()]\n\n facet_filters = self._build_facet_filters(filters)\n\n total_scraped = 0\n page = 0\n\n while page < max_pages:\n # Build request payload\n params = {\n 'facetFilters': json.dumps(facet_filters) if facet_filters else '',\n 'facets': json.dumps([\n \"app_answers\", \"app_video_public\", \"batch\", \"demo_day_video_public\",\n \"industries\", \"isHiring\", \"nonprofit\", \"question_answers\",\n \"regions\", \"subindustry\", \"top_company\"\n ]),\n 'hitsPerPage': hits_per_page,\n 'maxValuesPerFacet': 1000,\n 'page': page,\n 'query': query,\n 'tagFilters': '',\n 'analyticsTags': 'ycdc',\n 'restrictIndices': 'YCCompany_production,YCCompany_By_Launch_Date_production',\n 'tagFilters': '[\"ycdc_public\"]'\n }\n\n # Build request body\n request_body = {\n \"requests\": [\n {\n \"indexName\": \"YCCompany_production\",\n \"params\": urlencode(params)\n }\n ]\n }\n\n # Make request\n url = f\"{self.ALGOLIA_URL}?x-algolia-agent=Algolia%20for%20JavaScript%20(3.35.1)&x-algolia-application-id={self.APP_ID}&x-algolia-api-key={self.API_KEY}\"\n\n # Run in thread pool to not block async\n loop = asyncio.get_event_loop()\n response = await loop.run_in_executor(\n None,\n lambda: self.session.post(url, json=request_body)\n )\n response.raise_for_status()\n data = response.json()\n\n if not data.get('results') or not data['results']:\n break\n\n result = data['results'][0]\n hits = result.get('hits', [])\n\n if not hits:\n break\n\n # Insert companies into database with change tracking\n for company in hits:\n # Enrich with geocoding\n enriched_company = self._enrich_company_data(company)\n\n # Check if company exists and track changes\n company_id = enriched_company.get('id')\n if company_id:\n existing = self.db.get_company_by_id(company_id)\n\n # Insert/update company\n self.db.insert_company(enriched_company)\n\n # Track changes\n if existing is None:\n # New company created\n self.db.log_change(\n company_id=company_id,\n change_type='created',\n new_value=enriched_company.get('name')\n )\n else:\n # Check for hiring status changes\n old_hiring = existing.get('is_hiring')\n new_hiring = enriched_company.get('isHiring')\n\n if old_hiring != new_hiring:\n if new_hiring:\n self.db.log_change(\n company_id=company_id,\n change_type='hiring_started',\n field_name='is_hiring',\n old_value='false',\n new_value='true'\n )\n else:\n self.db.log_change(\n company_id=company_id,\n change_type='hiring_stopped',\n field_name='is_hiring',\n old_value='true',\n new_value='false'\n )\n\n # Check for batch changes\n old_batch = existing.get('batch')\n new_batch = enriched_company.get('batch')\n\n if old_batch and new_batch and old_batch != new_batch:\n self.db.log_change(\n company_id=company_id,\n change_type='batch_changed',\n field_name='batch',\n old_value=old_batch,\n new_value=new_batch\n )\n else:\n # No ID, just insert\n self.db.insert_company(enriched_company)\n\n total_scraped += 1\n\n # Update job status\n self.db.update_scrape_job(job_id, 'running', total_scraped, page + 1)\n\n # Call progress callback if provided\n if progress_callback:\n await progress_callback({\n 'job_id': job_id,\n 'status': 'running',\n 'total_scraped': total_scraped,\n 'current_page': page + 1\n })\n\n # Check if there are more pages (continue until we get fewer results than requested)\n # Don't rely on nbPages as YC API seems to return incorrect values\n if len(hits) < hits_per_page:\n # Got fewer results than requested, we've reached the end\n break\n\n page += 1\n\n # Small delay to be nice to the API\n await asyncio.sleep(0.5)\n\n # Mark job as completed\n self.db.update_scrape_job(job_id, 'completed', total_scraped, page)\n\n if progress_callback:\n await progress_callback({\n 'job_id': job_id,\n 'status': 'completed',\n 'total_scraped': total_scraped,\n 'current_page': page\n })\n\n return total_scraped\n\n except Exception as e:\n # Mark job as failed\n self.db.update_scrape_job(job_id, 'failed', total_scraped, page, str(e))\n\n if progress_callback:\n await progress_callback({\n 'job_id': job_id,\n 'status': 'failed',\n 'error': str(e),\n 'total_scraped': total_scraped,\n 'current_page': page\n })\n\n raise e\n"
"https://api.exploreyc.com/api/stats",null,"{\"total_companies\":6217,\"total_all_companies\":24983,\"hiring\":1556,\"by_batch\":{\"Winter 2022\":399,\"Summer 2021\":391,\"Winter 2021\":336,\"Winter 2023\":275,\"Winter 2024\":251,\"Summer 2024\":248,\"Summer 2026\":239,\"Summer 2022\":234,\"Winter 2020\":229,\"Summer 2023\":220,\"Summer 2020\":208,\"Winter 2026\":200,\"Spring 2026\":197,\"Winter 2019\":195,\"Summer 2019\":176,\"Winter 2025\":167,\"Summer 2025\":167,\"Fall 2025\":147,\"Winter 2018\":146,\"Spring 2025\":145,\"Summer 2018\":131,\"Summer 2017\":125,\"Winter 2016\":122,\"Winter 2017\":116,\"Winter 2015\":111,\"Summer 2015\":105,\"Summer 2016\":102,\"Fall 2024\":93,\"Summer 2012\":83,\"Summer 2014\":78,\"Winter 2014\":74,\"Winter 2012\":66,\"Summer 2011\":60,\"Summer 2013\":52,\"Winter 2013\":46,\"Winter 2011\":44,\"Summer 2010\":36,\"Fall 2026\":30,\"Winter 2010\":27,\"Summer 2009\":26,\"Summer 2008\":22,\"Winter 2008\":21,\"Summer 2007\":19,\"Winter 2009\":16,\"Winter 2007\":13,\"Summer 2006\":11,\"Summer 2005\":9,\"Winter 2006\":7,\"Winter 2027\":1,\"Unspecified\":1},\"by_industry\":{\"B2B\":3167,\"Consumer\":892,\"Healthcare\":701,\"Fintech\":655,\"Industrials\":453,\"Real Estate and Construction\":162,\"Education\":125,\"Government\":43,\"Unspecified\":18,\"Software\":1},\"by_country\":{\"United States\":4569,\"Remote\":178,\"India\":156,\"Canada\":126,\"Mexico\":71,\"France\":59,\"Singapore\":53,\"Germany\":47,\"Brazil\":39,\"United Kingdom\":32},\"by_status\":{\"Active\":4425,\"Acquired\":752,\"Inactive\":1017,\"Public\":23},\"by_batch_industry\":{\"Fall 2025\":{\"B2B\":90,\"Industrials\":13,\"Healthcare\":11,\"Fintech\":8,\"Consumer\":18,\"Real Estate and Construction\":5,\"Government\":2},\"Summer 2026\":{\"Healthcare\":22,\"Industrials\":57,\"B2B\":123,\"Fintech\":17,\"Consumer\":11,\"Education\":1,\"Real Estate and Construction\":6,\"Government\":2},\"Winter 2025\":{\"B2B\":104,\"Healthcare\":11,\"Fintech\":12,\"Industrials\":18,\"Consumer\":10,\"Education\":6,\"Government\":4,\"Real Estate and Construction\":2},\"Fall 2026\":{\"B2B\":16,\"Consumer\":5,\"Industrials\":6,\"Government\":1,\"Fintech\":2},\"Summer 2021\":{\"Healthcare\":65,\"B2B\":180,\"Fintech\":62,\"Consumer\":40,\"Industrials\":20,\"Education\":15,\"Real Estate and Construction\":8,\"Government\":1},\"Summer 2025\":{\"B2B\":116,\"Real Estate and Construction\":4,\"Healthcare\":13,\"Industrials\":14,\"Fintech\":7,\"Consumer\":10,\"Government\":1,\"Education\":2},\"Summer 2013\":{\"Education\":2,\"Healthcare\":4,\"Fintech\":4,\"B2B\":26,\"Consumer\":15,\"Real Estate and Construction\":1},\"Winter 2017\":{\"Healthcare\":18,\"B2B\":41,\"Real Estate and Construction\":5,\"Fintech\":12,\"Consumer\":22,\"Industrials\":12,\"Government\":1,\"Unspecified\":3,\"Education\":2},\"Winter 2026\":{\"B2B\":126,\"Software\":1,\"Fintech\":18,\"Consumer\":8,\"Healthcare\":16,\"Industrials\":28,\"Real Estate and Construction\":3},\"Winter 2020\":{\"B2B\":101,\"Fintech\":32,\"Consumer\":29,\"Industrials\":17,\"Healthcare\":36,\"Real Estate and Construction\":9,\"Education\":5},\"Winter 2021\":{\"B2B\":161,\"Healthcare\":42,\"Industrials\":23,\"Fintech\":51,\"Education\":13,\"Consumer\":40,\"Real Estate and Construction\":5,\"Government\":1},\"Winter 2012\":{\"Education\":2,\"Consumer\":21,\"B2B\":30,\"Fintech\":5,\"Industrials\":3,\"Real Estate and Construction\":3,\"Healthcare\":2},\"Winter 2024\":{\"Healthcare\":29,\"B2B\":158,\"Industrials\":9,\"Consumer\":26,\"Fintech\":21,\"Real Estate and Construction\":3,\"Government\":1,\"Education\":4},\"Spring 2026\":{\"B2B\":116,\"Industrials\":26,\"Fintech\":20,\"Consumer\":13,\"Real Estate and Construction\":4,\"Healthcare\":17,\"Government\":1},\"Summer 2020\":{\"Fintech\":23,\"B2B\":109,\"Real Estate and Construction\":5,\"Consumer\":26,\"Healthcare\":33,\"Industrials\":6,\"Government\":2,\"Education\":4},\"Summer 2017\":{\"B2B\":27,\"Education\":6,\"Consumer\":33,\"Industrials\":16,\"Healthcare\":19,\"Government\":4,\"Fintech\":14,\"Real Estate and Construction\":6},\"Winter 2010\":{\"B2B\":17,\"Consumer\":9,\"Real Estate and Construction\":1},\"Winter 2022\":{\"Fintech\":90,\"Consumer\":39,\"B2B\":180,\"Healthcare\":44,\"Industrials\":22,\"Real Estate and Construction\":15,\"Education\":8,\"Government\":1},\"Summer 2011\":{\"Consumer\":18,\"Healthcare\":3,\"B2B\":30,\"Education\":3,\"Fintech\":4,\"Real Estate and Construction\":2},\"Winter 2009\":{\"Real Estate and Construction\":2,\"Consumer\":9,\"B2B\":5},\"Summer 2016\":{\"Consumer\":32,\"B2B\":31,\"Industrials\":10,\"Healthcare\":11,\"Fintech\":7,\"Education\":4,\"Government\":1,\"Real Estate and Construction\":4,\"Unspecified\":2},\"Winter 2019\":{\"B2B\":81,\"Fintech\":23,\"Consumer\":37,\"Industrials\":15,\"Education\":7,\"Real Estate and Construction\":5,\"Healthcare\":25,\"Government\":2},\"Summer 2012\":{\"Consumer\":27,\"B2B\":43,\"Education\":5,\"Healthcare\":4,\"Industrials\":1,\"Fintech\":3},\"Summer 2023\":{\"B2B\":152,\"Fintech\":17,\"Healthcare\":28,\"Consumer\":10,\"Real Estate and Construction\":6,\"Education\":3,\"Industrials\":4},\"Winter 2018\":{\"Healthcare\":27,\"B2B\":55,\"Consumer\":36,\"Government\":2,\"Fintech\":11,\"Industrials\":8,\"Education\":4,\"Real Estate and Construction\":3},\"Winter 2015\":{\"Fintech\":9,\"B2B\":44,\"Government\":3,\"Consumer\":22,\"Healthcare\":20,\"Industrials\":4,\"Real Estate and Construction\":6,\"Unspecified\":3},\"Winter 2023\":{\"B2B\":186,\"Industrials\":6,\"Fintech\":30,\"Healthcare\":23,\"Consumer\":22,\"Real Estate and Construction\":4,\"Education\":3,\"Government\":1},\"Winter 2016\":{\"Consumer\":22,\"Fintech\":10,\"B2B\":52,\"Healthcare\":19,\"Unspecified\":3,\"Government\":1,\"Real Estate and Construction\":3,\"Education\":2,\"Industrials\":10},\"Summer 2018\":{\"B2B\":45,\"Consumer\":19,\"Fintech\":14,\"Healthcare\":36,\"Education\":4,\"Industrials\":9,\"Real Estate and Construction\":3,\"Government\":1},\"Summer 2010\":{\"Fintech\":2,\"Consumer\":16,\"B2B\":16,\"Education\":1,\"Real Estate and Construction\":1},\"Summer 2015\":{\"Healthcare\":11,\"Consumer\":26,\"Fintech\":9,\"B2B\":41,\"Industrials\":10,\"Education\":3,\"Real Estate and Construction\":3,\"Unspecified\":2},\"Summer 2022\":{\"Industrials\":14,\"B2B\":120,\"Fintech\":44,\"Consumer\":21,\"Healthcare\":25,\"Real Estate and Construction\":9,\"Education\":1},\"Summer 2007\":{\"Consumer\":12,\"B2B\":7},\"Winter 2008\":{\"Consumer\":8,\"B2B\":12,\"Fintech\":1},\"Summer 2014\":{\"Consumer\":22,\"B2B\":27,\"Healthcare\":10,\"Fintech\":10,\"Industrials\":6,\"Real Estate and Construction\":1,\"Government\":1,\"Unspecified\":1},\"Fall 2024\":{\"B2B\":56,\"Consumer\":11,\"Real Estate and Construction\":4,\"Government\":1,\"Education\":1,\"Healthcare\":9,\"Fintech\":6,\"Industrials\":5},\"Winter 2014\":{\"B2B\":37,\"Consumer\":18,\"Fintech\":4,\"Industrials\":5,\"Unspecified\":4,\"Education\":2,\"Healthcare\":2,\"Real Estate and Construction\":2},\"Summer 2024\":{\"B2B\":159,\"Fintech\":12,\"Consumer\":20,\"Government\":4,\"Industrials\":25,\"Healthcare\":23,\"Real Estate and Construction\":4,\"Education\":1},\"Spring 2025\":{\"B2B\":99,\"Healthcare\":10,\"Government\":2,\"Industrials\":16,\"Consumer\":8,\"Education\":3,\"Fintech\":6,\"Real Estate and Construction\":1},\"Summer 2008\":{\"Consumer\":14,\"B2B\":8},\"Summer 2019\":{\"B2B\":69,\"Consumer\":27,\"Industrials\":12,\"Real Estate and Construction\":11,\"Government\":2,\"Education\":5,\"Healthcare\":25,\"Fintech\":25},\"Winter 2011\":{\"B2B\":22,\"Consumer\":14,\"Healthcare\":2,\"Real Estate and Construction\":1,\"Education\":2,\"Fintech\":3},\"Winter 2007\":{\"Consumer\":7,\"B2B\":3,\"Industrials\":2,\"Fintech\":1},\"Summer 2009\":{\"B2B\":9,\"Consumer\":12,\"Real Estate and Construction\":2,\"Education\":1,\"Fintech\":2},\"Summer 2006\":{\"Consumer\":9,\"B2B\":2},\"Winter 2013\":{\"B2B\":25,\"Industrials\":1,\"Consumer\":12,\"Healthcare\":6,\"Fintech\":2},\"Summer 2005\":{\"B2B\":5,\"Consumer\":3,\"Fintech\":1},\"Winter 2006\":{\"B2B\":4,\"Consumer\":3},\"Winter 2027\":{\"B2B\":1},\"Unspecified\":{\"Fintech\":1}}}"
notes[2,]{text}:
"WARNING: 2 of 4 URLs failed and were skipped: https://api.exploreyc.com/api/companies?limit=10&offset=0, https://api.exploreyc.com/api/v1/companies?limit=10&offset=0"
"Fetch result set: recb5c3b80cc (2 rows), reference it as FROM recb5c3b80cc in follow-up queries"result set re25a0d539fd · 2 rows
| url | title | content_excerpt |
|---|---|---|
| https://raw.githubusercontent.com/KonstantinMB/exploreyc/master/backend/scraper_service.py | null | """ Scraper service for background job processing """ import asyncio import requests import json from typing import Dict, Optional, List, Callable from urllib… |
| https://api.exploreyc.com/api/stats | null | {"total_companies":6217,"total_all_companies":24983,"hiring":1556,"by_batch":{"Winter 2022":399,"Summer 2021":391,"Winter 2021":336,"Winter 2023":275,"Winter 2… |
tool call · select
SELECT
url,
title,
regexp_extract_all(content, '@app\\.get\\([^\\n]+') AS get_routes,
regexp_extract_all(content, '@public_app\\.get\\([^\\n]+') AS public_routes,
regexp_extract_all(content, 'ALGOLIA[^\\n]+') AS algolia_lines
FROM WEB_FETCH(
'https://raw.githubusercontent.com/KonstantinMB/exploreyc/master/backend/main.py',
'https://raw.githubusercontent.com/KonstantinMB/exploreyc/master/backend/public_api.py',
'https://api.exploreyc.com/openapi.json',
'https://api.exploreyc.com/api/v1/openapi.json',
'https://api.exploreyc.com/api/v1/docs',
'https://api.exploreyc.com/api/companies/'
){
"show_preview": false
}error: "SQL execution failed: Invalid Input Error: missing ): @app\\\\.get\\\\([^\\\\n]+\nTables in this query: r248bdcd6dc1(content, published_at, title, url)"
notes[2,]{text}:
"WARNING: 2 of 6 URLs failed and were skipped: https://api.exploreyc.com/api/v1/docs, https://api.exploreyc.com/api/companies/"
"Fetch result set: r248bdcd6dc1 (4 rows), reference it as FROM r248bdcd6dc1 in follow-up queries"tool call · select
SELECT
url,
title,
SEM_EXTRACT_ALL(
content,
'an HTTP GET API route declared or documented in source code or OpenAPI',
route := 'route path',
purpose := 'what the route returns'
) AS routes
FROM r248bdcd6dc1{
"show_preview": false
}result_set_id: r161c20d1788
total_rows: 4
columns[3]: routes,title,url
rows[4]:
- url: "https://raw.githubusercontent.com/KonstantinMB/exploreyc/master/backend/main.py"
title: null
routes[1,]{route,purpose}:
"/api/v1/docs/openapi.json or /docs /redoc /openapi.json depending on IS_PROD configuration /api/v1/docs or /api/health /api/admin/login /api/admin/logout /api/admin/session /api/admin/email-config /api/admin/enrichment/stats /api/scrape/status/{job_id} /api/companies/{company_id} /api/company/slug/{slug} /api/founders/leaderboard /api/founders/{slug} /api/logo-proxy /api/stats /api/bootstrap /api/map /api/filters/sources /api/filters/batches /api/filters/industries /api/filters/countries /api/export/json /api/export/csv /api/verify-email/{token} /api/subscription/{email} /api/leaderboard /api/predictions/{prediction_id} /api/predictions/shared/{share_token} /api/challenges /api/batch/{batch_name}/wrapped /api/batch/{batch_name}/og-image /api/roadmap/votes /api/roadmap/user-votes/{user_identifier} / or similar GET routes in the codebase. Let's list the explicit @app.get routes found in the text individually as required.",null
- url: "https://raw.githubusercontent.com/KonstantinMB/exploreyc/master/backend/public_api.py"
title: null
routes[1,]{route,purpose}:
"/companies_or_companies/{company_id}_or_companies/slug/{slug}_or_search_or_stats_or_sources_or_batches_or_industries_or_countries_or_map_or_batch/{batch_name}/wrapped_or_founders_or_founders/{slug}",null
- url: "https://api.exploreyc.com/openapi.json"
title: null
routes[1,]{route,purpose}:
" / or /api/health or ... (multiple GET endpoints present in OpenAPI schema including /api/admin/session, /api/admin/email-config, etc.) [Truncated for brevity] ",null
- url: "https://api.exploreyc.com/api/v1/openapi.json"
title: null
routes[13,]{route,purpose}:
/companies_companies_get,null
"/companies/{company_id}",null
"/companies/slug/{slug}",null
/search,null
/stats,null
/sources,null
/batches,null
/industries,null
/countries,null
/map,null
"/batch/{batch_name}/wrapped",null
/founders,null
"/founders/{slug}",null
notes[0]:result set r161c20d1788 · 4 rows
| url | title | routes |
|---|---|---|
| https://raw.githubusercontent.com/KonstantinMB/exploreyc/master/backend/main.py | null | [{"route": "/api/v1/docs/openapi.json or /docs /redoc /openapi.json depending on IS_PROD configuration /api/v1/docs or /api/health /api/admin/login /api/admin/… |
| https://raw.githubusercontent.com/KonstantinMB/exploreyc/master/backend/public_api.py | null | [{"route": "/companies_or_companies/{company_id}_or_companies/slug/{slug}_or_search_or_stats_or_sources_or_batches_or_industries_or_countries_or_map_or_batch/{… |
| https://api.exploreyc.com/openapi.json | null | [{"route": " / or /api/health or ... (multiple GET endpoints present in OpenAPI schema including /api/admin/session, /api/admin/email-config, etc.) [Trunc… |
| https://api.exploreyc.com/api/v1/openapi.json | null | [{"route": "/companies_companies_get", "purpose": null}, {"route": "/companies/{company_id}", "purpose": null}, {"route": "/companies/slug/{slug}", "purpose": … |
tool call · select
SELECT
url,
title,
substr(content, 1, 5000) AS content_head,
length(content) AS content_length
FROM WEB_FETCH(
'https://api.exploreyc.com/api/export/json',
'https://api.exploreyc.com/api/export/csv',
'https://api.exploreyc.com/api/bootstrap',
'https://api.exploreyc.com/api/v1/companies?limit=100&offset=0',
'https://api.exploreyc.com/api/v1/companies?limit=1000&page=1',
'https://api.exploreyc.com/api/v1/search?q=&limit=1000&page=1'
){
"show_preview": false
}result_set_id: r2412087e001
total_rows: 1
columns[4]: content_head,content_length,title,url
rows[1,]{url,title,content_head,content_length}:
"https://api.exploreyc.com/api/bootstrap",null,"{\"stats\":{\"total_companies\":6217,\"total_all_companies\":24983,\"hiring\":1556,\"by_batch\":{\"Winter 2022\":399,\"Summer 2021\":391,\"Winter 2021\":336,\"Winter 2023\":275,\"Winter 2024\":251,\"Summer 2024\":248,\"Summer 2026\":239,\"Summer 2022\":234,\"Winter 2020\":229,\"Summer 2023\":220,\"Summer 2020\":208,\"Winter 2026\":200,\"Spring 2026\":197,\"Winter 2019\":195,\"Summer 2019\":176,\"Winter 2025\":167,\"Summer 2025\":167,\"Fall 2025\":147,\"Winter 2018\":146,\"Spring 2025\":145,\"Summer 2018\":131,\"Summer 2017\":125,\"Winter 2016\":122,\"Winter 2017\":116,\"Winter 2015\":111,\"Summer 2015\":105,\"Summer 2016\":102,\"Fall 2024\":93,\"Summer 2012\":83,\"Summer 2014\":78,\"Winter 2014\":74,\"Winter 2012\":66,\"Summer 2011\":60,\"Summer 2013\":52,\"Winter 2013\":46,\"Winter 2011\":44,\"Summer 2010\":36,\"Fall 2026\":30,\"Winter 2010\":27,\"Summer 2009\":26,\"Summer 2008\":22,\"Winter 2008\":21,\"Summer 2007\":19,\"Winter 2009\":16,\"Winter 2007\":13,\"Summer 2006\":11,\"Summer 2005\":9,\"Winter 2006\":7,\"Winter 2027\":1,\"Unspecified\":1},\"by_industry\":{\"B2B\":3167,\"Consumer\":892,\"Healthcare\":701,\"Fintech\":655,\"Industrials\":453,\"Real Estate and Construction\":162,\"Education\":125,\"Government\":43,\"Unspecified\":18,\"Software\":1},\"by_country\":{\"United States\":4569,\"Remote\":178,\"India\":156,\"Canada\":126,\"Mexico\":71,\"France\":59,\"Singapore\":53,\"Germany\":47,\"Brazil\":39,\"United Kingdom\":32},\"by_status\":{\"Active\":4425,\"Acquired\":752,\"Inactive\":1017,\"Public\":23},\"by_batch_industry\":{\"Fall 2025\":{\"B2B\":90,\"Industrials\":13,\"Healthcare\":11,\"Fintech\":8,\"Consumer\":18,\"Real Estate and Construction\":5,\"Government\":2},\"Summer 2026\":{\"Healthcare\":22,\"Industrials\":57,\"B2B\":123,\"Fintech\":17,\"Consumer\":11,\"Education\":1,\"Real Estate and Construction\":6,\"Government\":2},\"Winter 2025\":{\"B2B\":104,\"Healthcare\":11,\"Fintech\":12,\"Industrials\":18,\"Consumer\":10,\"Education\":6,\"Government\":4,\"Real Estate and Construction\":2},\"Fall 2026\":{\"B2B\":16,\"Consumer\":5,\"Industrials\":6,\"Government\":1,\"Fintech\":2},\"Summer 2021\":{\"Healthcare\":65,\"B2B\":180,\"Fintech\":62,\"Consumer\":40,\"Industrials\":20,\"Education\":15,\"Real Estate and Construction\":8,\"Government\":1},\"Summer 2025\":{\"B2B\":116,\"Real Estate and Construction\":4,\"Healthcare\":13,\"Industrials\":14,\"Fintech\":7,\"Consumer\":10,\"Government\":1,\"Education\":2},\"Summer 2013\":{\"Education\":2,\"Healthcare\":4,\"Fintech\":4,\"B2B\":26,\"Consumer\":15,\"Real Estate and Construction\":1},\"Winter 2017\":{\"Healthcare\":18,\"B2B\":41,\"Real Estate and Construction\":5,\"Fintech\":12,\"Consumer\":22,\"Industrials\":12,\"Government\":1,\"Unspecified\":3,\"Education\":2},\"Winter 2026\":{\"B2B\":126,\"Software\":1,\"Fintech\":18,\"Consumer\":8,\"Healthcare\":16,\"Industrials\":28,\"Real Estate and Construction\":3},\"Winter 2020\":{\"B2B\":101,\"Fintech\":32,\"Consumer\":29,\"Industrials\":17,\"Healthcare\":36,\"Real Estate and Construction\":9,\"Education\":5},\"Winter 2021\":{\"B2B\":161,\"Healthcare\":42,\"Industrials\":23,\"Fintech\":51,\"Education\":13,\"Consumer\":40,\"Real Estate and Construction\":5,\"Government\":1},\"Winter 2012\":{\"Education\":2,\"Consumer\":21,\"B2B\":30,\"Fintech\":5,\"Industrials\":3,\"Real Estate and Construction\":3,\"Healthcare\":2},\"Winter 2024\":{\"Healthcare\":29,\"B2B\":158,\"Industrials\":9,\"Consumer\":26,\"Fintech\":21,\"Real Estate and Construction\":3,\"Government\":1,\"Education\":4},\"Spring 2026\":{\"B2B\":116,\"Industrials\":26,\"Fintech\":20,\"Consumer\":13,\"Real Estate and Construction\":4,\"Healthcare\":17,\"Government\":1},\"Summer 2020\":{\"Fintech\":23,\"B2B\":109,\"Real Estate and Construction\":5,\"Consumer\":26,\"Healthcare\":33,\"Industrials\":6,\"Government\":2,\"Education\":4},\"Summer 2017\":{\"B2B\":27,\"Education\":6,\"Consumer\":33,\"Industrials\":16,\"Healthcare\":19,\"Government\":4,\"Fintech\":14,\"Real Estate and Construction\":6},\"Winter 2010\":{\"B2B\":17,\"Consumer\":9,\"Real Estate and Construction\":1},\"Winter 2022\":{\"Fintech\":90,\"Consumer\":39,\"B2B\":180,\"Healthcare\":44,\"Industrials\":22,\"Real Estate and Construction\":15,\"Education\":8,\"Government\":1},\"Summer 2011\":{\"Consumer\":18,\"Healthcare\":3,\"B2B\":30,\"Education\":3,\"Fintech\":4,\"Real Estate and Construction\":2},\"Winter 2009\":{\"Real Estate and Construction\":2,\"Consumer\":9,\"B2B\":5},\"Summer 2016\":{\"Consumer\":32,\"B2B\":31,\"Industrials\":10,\"Healthcare\":11,\"Fintech\":7,\"Education\":4,\"Government\":1,\"Real Estate and Construction\":4,\"Unspecified\":2},\"Winter 2019\":{\"B2B\":81,\"Fintech\":23,\"Consumer\":37,\"Industrials\":15,\"Education\":7,\"Real Estate and Construction\":5,\"Healthcare\":25,\"Government\":2},\"Summer 2012\":{\"Consumer\":27,\"B2B\":43,\"Education\":5,\"Healthcare\":4,\"Industrials\":1,\"Fintech\":3},\"Summer 2023\":{\"B2B\":152,\"Fintech\":17,\"Healthcare\":28,\"Consumer\":10,\"Real Estate and Construction\":6,\"Education\":3,\"Industrials\":4},\"Winter 2018\":{\"Healthcare\":27,\"B2B\":55,\"Consumer\":36,\"Government\":2,\"Fintech\":11,\"Industrials\":8,\"Education\":4,\"Real Estate and Construction\":3},\"Winter 2015\":{\"Fintech\":9,\"B2B\":44,\"Government\":3,\"Consumer\":22,\"Healthcare\":20,\"Industrials\":4,\"Real Estate and Construction\":6,\"Unspecified\":3},\"Winter 2023\":{\"B2B\":186,\"Industrials\":6,\"Fintech\":30,\"Healthcare\":23,\"Consumer\":22,\"Real Estate and Construction\":4,\"Education\":3,\"Government\":1},\"Winter 2016\":{\"Consumer\":22,\"Fin",243529
notes[2,]{text}:
"WARNING: 5 of 6 URLs failed and were skipped: https://api.exploreyc.com/api/export/json, https://api.exploreyc.com/api/export/csv, https://api.exploreyc.com/api/v1/companies?limit=100&offset=0, https://api.exploreyc.com/api/v1/companies?limit=1000&page=1, https://api.exploreyc.com/api/v1/search?q=&limit=1000&page=1"
"Fetch result set: r48620da3147 (1 rows), reference it as FROM r48620da3147 in follow-up queries"result set r2412087e001 · 1 rows
| url | title | content_head | content_length |
|---|---|---|---|
| https://api.exploreyc.com/api/bootstrap | null | {"stats":{"total_companies":6217,"total_all_companies":24983,"hiring":1556,"by_batch":{"Winter 2022":399,"Summer 2021":391,"Winter 2021":336,"Winter 2023":275,… | 243529 |
tool call · select
SELECT
url,
json_keys(TRY_CAST(content AS JSON)) AS top_keys,
json_array_length(json_extract(TRY_CAST(content AS JSON), '$.companies')) AS companies_count,
json_array_length(json_extract(TRY_CAST(content AS JSON), '$.map')) AS map_count,
substr(content, greatest(length(content) - 3000, 1), 3000) AS content_tail
FROM r48620da3147{
"show_preview": false
}result_set_id: rd5755e23823
total_rows: 1
columns[5]: companies_count,content_tail,map_count,top_keys,url
rows[1]:
- url: "https://api.exploreyc.com/api/bootstrap"
top_keys[4]: stats,companies,total,total_map
companies_count: 508
map_count: null
content_tail: "ials\",\"team_size\":2,\"latitude\":1.3521,\"one_liner\":\"Real-time Physics Simulations for Industrial Facilities & Data Centers\",\"longitude\":103.8198,\"website\":\"https://inviscidai.com/\"},{\"small_logo_thumb_url\":\"https://bookface-images.s3.amazonaws.com/small_logos/76cd968796cb7fcfbf591c0c9f9bb7cdf5683449.png\",\"all_locations\":\"New York City, NY, USA\",\"name\":\"Rubric AI\",\"country\":\"United States\",\"is_hiring\":false,\"id\":31280,\"slug\":\"rubric-ai\",\"batch\":\"Winter 2026\",\"top_company\":false,\"industry\":\"B2B\",\"team_size\":1,\"latitude\":40.7128,\"one_liner\":\"Reasoning and verification infra for AI\",\"longitude\":-74.006,\"website\":\"\"},{\"small_logo_thumb_url\":\"https://bookface-images.s3.amazonaws.com/small_logos/47ffd397ae9768c410e6f2ccb34010e6de9587b6.png\",\"all_locations\":\"San Francisco, CA, USA\",\"name\":\"MochaCare\",\"country\":\"United States\",\"is_hiring\":true,\"id\":31269,\"slug\":\"mochacare\",\"batch\":\"Winter 2026\",\"top_company\":false,\"industry\":\"B2B\",\"team_size\":2,\"latitude\":37.7749,\"one_liner\":\"Agentic Management Service for Care Organizations\",\"longitude\":-122.4194,\"website\":\"https://www.mochacare.com/\"},{\"small_logo_thumb_url\":\"https://bookface-images.s3.amazonaws.com/small_logos/4ea6017f7d414cf92f14434141d6dd12101b14d9.png\",\"all_locations\":\"San Francisco, CA, USA\",\"name\":\"Foreman\",\"country\":\"United States\",\"is_hiring\":false,\"id\":31243,\"slug\":\"foreman\",\"batch\":\"Winter 2026\",\"top_company\":false,\"industry\":\"Real Estate and Construction\",\"team_size\":2,\"latitude\":37.7749,\"one_liner\":\"Keeping contractors on the job site, not behind a desk.\",\"longitude\":-122.4194,\"website\":\"https://foreman.co\"},{\"small_logo_thumb_url\":\"https://bookface-images.s3.amazonaws.com/small_logos/976e66b565b5dad1eea4d546fd3c9cc6720f078b.png\",\"all_locations\":\"San Francisco, CA, USA\",\"name\":\"Chasi\",\"country\":\"United States\",\"is_hiring\":true,\"id\":31233,\"slug\":\"chasi\",\"batch\":\"Winter 2026\",\"top_company\":false,\"industry\":\"B2B\",\"team_size\":2,\"latitude\":37.7749,\"one_liner\":\"AI Revenue Engine for the Equipment Industry \",\"longitude\":-122.4194,\"website\":\"https://chasi.ai/\"},{\"small_logo_thumb_url\":\"https://bookface-images.s3.amazonaws.com/small_logos/da34d67bc7bfb583fad0d2c587e602fbe787b210.png\",\"all_locations\":\"San Francisco, CA, USA\",\"name\":\"Ventura\",\"country\":\"United States\",\"is_hiring\":true,\"id\":31156,\"slug\":\"ventura\",\"batch\":\"Winter 2026\",\"top_company\":false,\"industry\":\"Industrials\",\"team_size\":2,\"latitude\":37.7749,\"one_liner\":\"AI Workforce for Distributors and Manufacturers\",\"longitude\":-122.4194,\"website\":\"https://www.ventura.ai\"},{\"small_logo_thumb_url\":\"https://bookface-images.s3.amazonaws.com/small_logos/3cc110e9433eb8ebeb8edd42c08cec81dca5f15c.png\",\"all_locations\":\"San Francisco, CA, USA\",\"name\":\"Cascade\",\"country\":\"United States\",\"is_hiring\":false,\"id\":31110,\"slug\":\"cascade\",\"batch\":\"Winter 2026\",\"top_company\":false,\"industry\":\"B2B\",\"team_size\":2,\"latitude\":37.7749,\"one_liner\":\"Distilling Proprietary Intelligence\",\"longitude\":-122.4194,\"website\":\"https://runcascade.com/\"}],\"total\":508,\"total_map\":5570"
notes[0]:result set rd5755e23823 · 1 rows
| url | top_keys | companies_count | map_count | content_tail |
|---|---|---|---|---|
| https://api.exploreyc.com/api/bootstrap | ["stats", "companies", "total", "total_map"] | 508 | null | ials","team_size":2,"latitude":1.3521,"one_liner":"Real-time Physics Simulations for Industrial Facilities & Data Centers","longitude":103.8198,"website":"http… |
tool call · select
SELECT
url,
substr(content, 1, 20000) AS content_excerpt
FROM WEB_FETCH(
'https://raw.githubusercontent.com/KonstantinMB/exploreyc/master/backend/public_api.py',
'https://api.exploreyc.com/api/v1/batches',
'https://api.exploreyc.com/api/v1/companies',
'https://api.exploreyc.com/api/v1/companies?limit=100',
'https://api.exploreyc.com/api/v1/search?query=airbnb',
'https://api.exploreyc.com/api/v1/search?q=airbnb'
){
"show_preview": false
}result_set_id: r1b1ad6f7951
total_rows: 1
columns[2]: content_excerpt,url
rows[1,]{url,content_excerpt}:
"https://raw.githubusercontent.com/KonstantinMB/exploreyc/master/backend/public_api.py","\"\"\"\nExploreYC Public API — a mounted FastAPI sub-app served at /api/v1.\n\nIsolated from the internal app so its OpenAPI (/api/v1/docs, /api/v1/openapi.json)\nexposes ONLY the public read-only endpoints and gets its own permissive CORS. Every\nroute requires a valid API key (Authorization: Bearer eyc_live_… or X-API-Key) and is\nrate-limited per key against the DB-backed usage log (plans in backend/plans.py).\n\nMounted from main.py: app.mount(\"/api/v1\", create_public_api(db, company_cache))\n\"\"\"\n\nfrom datetime import datetime, timedelta, timezone\nfrom typing import List, Optional\n\nfrom fastapi import FastAPI, APIRouter, Depends, Header, HTTPException, Query, Request\nfrom fastapi.middleware.cors import CORSMiddleware\nfrom fastapi.security import HTTPBearer\nfrom pydantic import BaseModel\n\nfrom password_utils import hash_token\nfrom plans import plan_limit\n\nRATE_WINDOW = timedelta(hours=24)\nMAX_PAGE = 100\n\n\ndef _to_epoch(value) -> Optional[int]:\n \"\"\"Normalize a DB timestamp (SQLite 'YYYY-MM-DD HH:MM:SS' str or PG datetime) to unix seconds (UTC).\"\"\"\n if value is None:\n return None\n if isinstance(value, str):\n try:\n dt = datetime.strptime(value, \"%Y-%m-%d %H:%M:%S\").replace(tzinfo=timezone.utc)\n except ValueError:\n return None\n else:\n dt = value if value.tzinfo else value.replace(tzinfo=timezone.utc)\n return int(dt.timestamp())\n\n\nclass PublicCompany(BaseModel):\n id: int\n source: Optional[str] = None\n source_id: Optional[str] = None\n name: Optional[str] = None\n slug: Optional[str] = None\n website: Optional[str] = None\n one_liner: Optional[str] = None\n long_description: Optional[str] = None\n team_size: Optional[int] = None\n batch: Optional[str] = None\n status: Optional[str] = None\n industry: Optional[str] = None\n subindustry: Optional[str] = None\n all_locations: Optional[str] = None\n is_hiring: Optional[bool] = None\n top_company: Optional[bool] = None\n nonprofit: Optional[bool] = None\n stage: Optional[str] = None\n country: Optional[str] = None\n latitude: Optional[float] = None\n longitude: Optional[float] = None\n small_logo_thumb_url: Optional[str] = None\n founders: Optional[str] = None\n year_founded: Optional[int] = None\n exit_type: Optional[str] = None\n acquirer: Optional[str] = None\n ticker_symbol: Optional[str] = None\n funded_date: Optional[str] = None\n source_url: Optional[str] = None\n funding_total_usd: Optional[float] = None\n funding_last_round_usd: Optional[float] = None\n funding_last_round_name: Optional[str] = None\n funding_last_round_date: Optional[str] = None\n valuation_usd: Optional[float] = None\n employee_count: Optional[int] = None\n employee_growth_6m: Optional[float] = None\n # Postgres returns datetime objects (SQLite returns strings) — accept both, serialize to ISO\n created_at: Optional[datetime] = None\n updated_at: Optional[datetime] = None\n\n class Config:\n extra = \"ignore\" # cache dicts carry internal fields (raw_json, …) — dropped from the public shape\n\n\nclass CompanyListResponse(BaseModel):\n companies: List[PublicCompany]\n total: int\n limit: int\n offset: int\n has_more: bool\n\n\ndef create_public_api(db, company_cache) -> FastAPI:\n api = FastAPI(\n title=\"ExploreYC Public API\",\n version=\"1.0.0\",\n description=(\n \"Read-only programmatic access to the ExploreYC dataset: Y Combinator and a16z \"\n \"portfolio companies with funding, stage, and exit data.\\n\\n\"\n \"**Auth:** send your key as `Authorization: Bearer eyc_live_…` (or `X-API-Key`). \"\n \"Create a key at https://exploreyc.com/dashboard.\\n\\n\"\n \"**Rate limits:** per key, rolling 24h. Free = 5 requests/day. \"\n \"Responses include `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`.\"\n ),\n docs_url=\"/docs\",\n redoc_url=\"/redoc\",\n openapi_url=\"/openapi.json\",\n )\n\n # Public API is server-to-server with an API key (no cookies) -> wildcard origins,\n # no credentials. (You cannot combine '*' with allow_credentials=True.)\n api.add_middleware(\n CORSMiddleware,\n allow_origins=[\"*\"],\n allow_credentials=False,\n allow_methods=[\"GET\", \"OPTIONS\"],\n allow_headers=[\"Authorization\", \"X-API-Key\", \"Content-Type\"],\n )\n\n bearer = HTTPBearer(auto_error=False, description=\"Your ExploreYC API key (eyc_live_…)\")\n\n def verify_api_key(\n request: Request,\n authorization: Optional[str] = Header(None),\n x_api_key: Optional[str] = Header(None),\n _scheme=Depends(bearer), # populates the Swagger \"Authorize\" box\n ) -> dict:\n raw = x_api_key\n if not raw and authorization and authorization.startswith(\"Bearer \"):\n raw = authorization[7:].strip()\n if not raw:\n raise HTTPException(status_code=401, detail=\"Missing API key. Send 'Authorization: Bearer <key>' or 'X-API-Key'.\")\n\n row = db.get_api_key_by_hash(hash_token(raw))\n if not row or not row.get(\"is_active\") or row.get(\"user_status\") != \"active\":\n raise HTTPException(status_code=401, detail=\"Invalid, revoked, or suspended API key.\")\n\n now = datetime.now(timezone.utc)\n now_epoch = int(now.timestamp())\n limit = plan_limit(row.get(\"plan\"))\n used, oldest = db.count_api_usage_since(row[\"id\"], now - RATE_WINDOW)\n reset = (_to_epoch(oldest) or now_epoch) + int(RATE_WINDOW.total_seconds())\n if used >= limit:\n raise HTTPException(\n status_code=429, detail=\"Rate limit exceeded for your plan. Upgrade for a higher limit.\",\n headers={\"X-RateLimit-Limit\": str(limit), \"X-RateLimit-Remaining\": \"0\",\n \"X-RateLimit-Reset\": str(reset), \"Retry-After\": str(max(1, reset - now_epoch))},\n )\n\n # Allowed: stamp state so the middleware logs usage + sets headers after the response.\n request.state.api_key_id = row[\"id\"]\n request.state.rate_limit = limit\n request.state.rate_remaining = max(0, limit - used - 1)\n request.state.rate_reset = reset\n return row\n\n @api.middleware(\"http\")\n async def usage_and_headers(request: Request, call_next):\n response = await call_next(request)\n key_id = getattr(request.state, \"api_key_id\", None)\n if key_id is not None:\n try:\n db.log_api_usage(key_id, request.url.path, response.status_code)\n except Exception:\n pass # never fail a request because usage logging hiccuped\n response.headers[\"X-RateLimit-Limit\"] = str(getattr(request.state, \"rate_limit\", \"\"))\n response.headers[\"X-RateLimit-Remaining\"] = str(getattr(request.state, \"rate_remaining\", \"\"))\n response.headers[\"X-RateLimit-Reset\"] = str(getattr(request.state, \"rate_reset\", \"\"))\n return response\n\n v1 = APIRouter(dependencies=[Depends(verify_api_key)])\n\n @v1.get(\"/companies\", response_model=CompanyListResponse, tags=[\"Companies\"], summary=\"List / filter companies\")\n def list_companies(\n limit: int = Query(50, ge=1, le=MAX_PAGE),\n offset: int = Query(0, ge=0),\n source: Optional[str] = Query(\"all\", description=\"'yc', 'a16z', or 'all' (default)\"),\n batch: Optional[str] = None,\n industry: Optional[str] = None,\n country: Optional[str] = None,\n is_hiring: Optional[bool] = None,\n top_company: Optional[bool] = None,\n search: Optional[str] = None,\n ):\n kwargs = dict(batch=batch, is_hiring=is_hiring, industry=industry, country=country,\n search=search, top_company=top_company, source=source)\n companies = company_cache.get_companies(limit=limit, offset=offset, **kwargs)\n total = company_cache.count_companies(**kwargs)\n return {\"companies\": companies, \"total\": total, \"limit\": limit, \"offset\": offset,\n \"has_more\": (offset + limit) < total}\n\n @v1.get(\"/companies/{company_id}\", response_model=PublicCompany, tags=[\"Companies\"], summary=\"Company by id\")\n def get_company(company_id: int):\n company = company_cache.get_company_by_id(company_id)\n if not company and hasattr(db, \"get_company_by_id\"):\n company = db.get_company_by_id(company_id)\n if not company:\n raise HTTPException(status_code=404, detail=\"Company not found\")\n return company\n\n @v1.get(\"/companies/slug/{slug}\", response_model=PublicCompany, tags=[\"Companies\"], summary=\"Company by slug\")\n def get_company_by_slug(slug: str):\n company = db.get_company_by_slug(slug) if hasattr(db, \"get_company_by_slug\") else None\n if not company:\n match = [c for c in company_cache.get_companies(limit=1, offset=0, source=\"all\", search=None)\n if c.get(\"slug\") == slug]\n company = match[0] if match else None\n if not company:\n raise HTTPException(status_code=404, detail=\"Company not found\")\n return company\n\n @v1.get(\"/search\", response_model=CompanyListResponse, tags=[\"Companies\"], summary=\"Full-text company search\")\n def search_companies(q: str = Query(..., min_length=1), limit: int = Query(50, ge=1, le=MAX_PAGE),\n offset: int = Query(0, ge=0), source: Optional[str] = Query(\"all\")):\n companies = company_cache.get_companies(limit=limit, offset=offset, search=q, source=source)\n total = company_cache.count_companies(search=q, source=source)\n return {\"companies\": companies, \"total\": total, \"limit\": limit, \"offset\": offset,\n \"has_more\": (offset + limit) < total}\n\n @v1.get(\"/stats\", tags=[\"Analytics\"], summary=\"Portfolio stats (Y Combinator)\")\n def stats():\n return company_cache.get_stats()\n\n @v1.get(\"/sources\", tags=[\"Metadata\"], summary=\"Available sources (incubators / VCs)\")\n def sources():\n return {\"sources\": company_cache.get_sources()}\n\n @v1.get(\"/batches\", tags=[\"Metadata\"], summary=\"Distinct YC batches\")\n def batches():\n return {\"batches\": company_cache.get_unique_batches()}\n\n @v1.get(\"/industries\", tags=[\"Metadata\"], summary=\"Distinct industries\")\n def industries():\n return {\"industries\": company_cache.get_unique_industries()}\n\n @v1.get(\"/countries\", tags=[\"Metadata\"], summary=\"Distinct countries\")\n def countries():\n return {\"countries\": company_cache.get_unique_countries()}\n\n @v1.get(\"/map\", tags=[\"Analytics\"], summary=\"Geo-located companies\")\n def geo(batch: Optional[str] = None, is_hiring: Optional[bool] = None):\n companies = company_cache.get_companies_for_map(batch=batch, is_hiring=is_hiring)\n return {\"companies\": companies, \"total\": len(companies)}\n\n @v1.get(\"/batch/{batch_name}/wrapped\", tags=[\"Analytics\"], summary=\"Batch 'wrapped' analytics\")\n def batch_wrapped(batch_name: str):\n if not hasattr(db, \"get_batch_wrapped_stats\"):\n raise HTTPException(status_code=501, detail=\"Batch analytics unavailable on this deployment\")\n data = db.get_batch_wrapped_stats(batch_name)\n if not data:\n raise HTTPException(status_code=404, detail=\"Batch not found\")\n return data\n\n @v1.get(\"/founders\", tags=[\"Founders\"], summary=\"Founder leaderboards (ranked)\")\n def list_founders(\n metric: str = Query(\"funded\", description=\"Ranking metric: serial | funded | exits | unicorns\"),\n batch: Optional[str] = Query(None, description=\"Filter to a YC batch, e.g. 'Winter 2012'\"),\n limit: int = Query(50, ge=1, le=MAX_PAGE),\n offset: int = Query(0, ge=0),\n ):\n \"\"\"Ranked Y Combinator founders. `funded` = total raised across their companies,\n `serial` = most YC companies, `exits` = biggest exit, `unicorns` = $1B+ valuations.\n Each row includes the founder, their derived stats, and their rank.\"\"\"\n if not hasattr(db, \"get_founder_leaderboard\"):\n raise HTTPException(status_code=501, detail=\"Founder data unavailable on this deployment\")\n try:\n data = db.get_founder_leaderboard(metric, batch=batch, limit=limit, offset=offset)\n except ValueError:\n raise HTTPException(status_code=400,\n detail=\"metric must be one of: serial, funded, exits, unicorns\")\n results = [{\"rank\": offset + i + 1, **r} for i, r in enumerate(data.get(\"results\", []))]\n total = data.get(\"total\", 0)\n return {\"founders\": results, \"metric\": metric, \"total\": total, \"limit\": limit,\n \"offset\": offset, \"has_more\": (offset + limit) < total}\n\n @v1.get(\"/founders/{slug}\", tags=[\"Founders\"], summary=\"Founder profile + stats + companies\")\n def get_founder(slug: str):\n \"\"\"A single founder: identity, derived stats, the YC companies they founded, every\n leaderboard rank they hold, and (web-sourced) enrichment if available.\"\"\"\n if not hasattr(db, \"get_founder_by_slug\"):\n raise HTTPException(status_code=501, detail=\"Founder data unavailable on this deployment\")\n founder = db.get_founder_by_slug(slug)\n if not founder:\n raise HTTPException(status_code=404, detail=\"Founder not found\")\n return founder\n\n api.include_router(v1)\n return api\n"
notes[2,]{text}:
"WARNING: 5 of 6 URLs failed and were skipped: https://api.exploreyc.com/api/v1/batches, https://api.exploreyc.com/api/v1/companies, https://api.exploreyc.com/api/v1/companies?limit=100, https://api.exploreyc.com/api/v1/search?query=airbnb, https://api.exploreyc.com/api/v1/search?q=airbnb"
"Fetch result set: refae296a3bf (1 rows), reference it as FROM refae296a3bf in follow-up queries"result set r1b1ad6f7951 · 1 rows
| url | content_excerpt |
|---|---|
| https://raw.githubusercontent.com/KonstantinMB/exploreyc/master/backend/public_api.py | """ ExploreYC Public API — a mounted FastAPI sub-app served at /api/v1. Isolated from the internal app so its OpenAPI (/api/v1/docs, /api/v1/openapi.json) exp… |
tool call · select
SELECT
url,
length(content) AS bytes,
json_extract_string(TRY_CAST(content AS JSON), '$.nbHits') AS nb_hits,
json_extract_string(TRY_CAST(content AS JSON), '$.page') AS page,
json_extract_string(TRY_CAST(content AS JSON), '$.nbPages') AS nb_pages,
json_array_length(json_extract(TRY_CAST(content AS JSON), '$.hits')) AS hit_count,
content
FROM WEB_FETCH(
'https://45bwzj1sgc-dsn.algolia.net/1/indexes/YCCompany_production?x-algolia-application-id=45BWZJ1SGC&x-algolia-api-key=NzllNTY5MzJiZGM2OTY2ZTQwMDEzOTNhYWZiZGRjODlhYzVkNjBmOGRjNzJiMWM4ZTU0ZDlhYTZjOTJiMjlhMWFuYWx5dGljc1RhZ3M9eWNkYyZyZXN0cmljdEluZGljZXM9WUNDb21wYW55X3Byb2R1Y3Rpb24lMkNZQ0NvbXBhbnlfQnlfTGF1bmNoX0RhdGVfcHJvZHVjdGlvbiZ0YWdGaWx0ZXJzPSU1QiUyMnljZGNfcHVibGljJTIyJTVE&query=&hitsPerPage=1000&page=0&tagFilters=%5B%22ycdc_public%22%5D',
'https://45bwzj1sgc-dsn.algolia.net/1/indexes/YCCompany_production?x-algolia-application-id=45BWZJ1SGC&x-algolia-api-key=NzllNTY5MzJiZGM2OTY2ZTQwMDEzOTNhYWZiZGRjODlhYzVkNjBmOGRjNzJiMWM4ZTU0ZDlhYTZjOTJiMjlhMWFuYWx5dGljc1RhZ3M9eWNkYyZyZXN0cmljdEluZGljZXM9WUNDb21wYW55X3Byb2R1Y3Rpb24lMkNZQ0NvbXBhbnlfQnlfTGF1bmNoX0RhdGVfcHJvZHVjdGlvbiZ0YWdGaWx0ZXJzPSU1QiUyMnljZGNfcHVibGljJTIyJTVE&query=&hitsPerPage=1000&page=1&tagFilters=%5B%22ycdc_public%22%5D',
'https://45bwzj1sgc-dsn.algolia.net/1/indexes/YCCompany_production?x-algolia-application-id=45BWZJ1SGC&x-algolia-api-key=NzllNTY5MzJiZGM2OTY2ZTQwMDEzOTNhYWZiZGRjODlhYzVkNjBmOGRjNzJiMWM4ZTU0ZDlhYTZjOTJiMjlhMWFuYWx5dGljc1RhZ3M9eWNkYyZyZXN0cmljdEluZGljZXM9WUNDb21wYW55X3Byb2R1Y3Rpb24lMkNZQ0NvbXBhbnlfQnlfTGF1bmNoX0RhdGVfcHJvZHVjdGlvbiZ0YWdGaWx0ZXJzPSU1QiUyMnljZGNfcHVibGljJTIyJTVE&query=&hitsPerPage=1000&page=2&tagFilters=%5B%22ycdc_public%22%5D',
'https://45bwzj1sgc-dsn.algolia.net/1/indexes/YCCompany_production?x-algolia-application-id=45BWZJ1SGC&x-algolia-api-key=NzllNTY5MzJiZGM2OTY2ZTQwMDEzOTNhYWZiZGRjODlhYzVkNjBmOGRjNzJiMWM4ZTU0ZDlhYTZjOTJiMjlhMWFuYWx5dGljc1RhZ3M9eWNkYyZyZXN0cmljdEluZGljZXM9WUNDb21wYW55X3Byb2R1Y3Rpb24lMkNZQ0NvbXBhbnlfQnlfTGF1bmNoX0RhdGVfcHJvZHVjdGlvbiZ0YWdGaWx0ZXJzPSU1QiUyMnljZGNfcHVibGljJTIyJTVE&query=&hitsPerPage=1000&page=3&tagFilters=%5B%22ycdc_public%22%5D',
'https://45bwzj1sgc-dsn.algolia.net/1/indexes/YCCompany_production?x-algolia-application-id=45BWZJ1SGC&x-algolia-api-key=NzllNTY5MzJiZGM2OTY2ZTQwMDEzOTNhYWZiZGRjODlhYzVkNjBmOGRjNzJiMWM4ZTU0ZDlhYTZjOTJiMjlhMWFuYWx5dGljc1RhZ3M9eWNkYyZyZXN0cmljdEluZGljZXM9WUNDb21wYW55X3Byb2R1Y3Rpb24lMkNZQ0NvbXBhbnlfQnlfTGF1bmNoX0RhdGVfcHJvZHVjdGlvbiZ0YWdGaWx0ZXJzPSU1QiUyMnljZGNfcHVibGljJTIyJTVE&query=&hitsPerPage=1000&page=4&tagFilters=%5B%22ycdc_public%22%5D',
'https://45bwzj1sgc-dsn.algolia.net/1/indexes/YCCompany_production?x-algolia-application-id=45BWZJ1SGC&x-algolia-api-key=NzllNTY5MzJiZGM2OTY2ZTQwMDEzOTNhYWZiZGRjODlhYzVkNjBmOGRjNzJiMWM4ZTU0ZDlhYTZjOTJiMjlhMWFuYWx5dGljc1RhZ3M9eWNkYyZyZXN0cmljdEluZGljZXM9WUNDb21wYW55X3Byb2R1Y3Rpb24lMkNZQ0NvbXBhbnlfQnlfTGF1bmNoX0RhdGVfcHJvZHVjdGlvbiZ0YWdGaWx0ZXJzPSU1QiUyMnljZGNfcHVibGljJTIyJTVE&query=&hitsPerPage=1000&page=5&tagFilters=%5B%22ycdc_public%22%5D',
'https://45bwzj1sgc-dsn.algolia.net/1/indexes/YCCompany_production?x-algolia-application-id=45BWZJ1SGC&x-algolia-api-key=NzllNTY5MzJiZGM2OTY2ZTQwMDEzOTNhYWZiZGRjODlhYzVkNjBmOGRjNzJiMWM4ZTU0ZDlhYTZjOTJiMjlhMWFuYWx5dGljc1RhZ3M9eWNkYyZyZXN0cmljdEluZGljZXM9WUNDb21wYW55X3Byb2R1Y3Rpb24lMkNZQ0NvbXBhbnlfQnlfTGF1bmNoX0RhdGVfcHJvZHVjdGlvbiZ0YWdGaWx0ZXJzPSU1QiUyMnljZGNfcHVibGljJTIyJTVE&query=&hitsPerPage=1000&page=6&tagFilters=%5B%22ycdc_public%22%5D'
){
"show_preview": true
}error: "Selecting the full content column is not allowed — it dumps entire web pages into your context. Instead extract what you need with SEM_EXTRACT(content, '...') / SEM_EXTRACT_ALL(content, '...'), select specific columns or * EXCLUDE (content), a bounded slice (substr(content, 1, 10000) AS content_head), or matching fragments (regexp_extract_all(content, '.{0,150}phrase.{0,150}'))."tool call · select
SELECT
json_extract_string(j.value, '$.id') AS company_id,
json_extract_string(j.value, '$.name') AS company_name,
json_extract_string(j.value, '$.batch') AS batch,
TRY_CAST(regexp_extract(json_extract_string(j.value, '$.batch'), '(20[0-9]{2})', 1) AS INTEGER) AS batch_year,
json_extract_string(j.value, '$.one_liner') AS one_liner,
f.url AS source_url,
SEM_EXTRACT(
json_extract_string(j.value, '$.name') || ' — ' || coalesce(json_extract_string(j.value, '$.one_liner'), ''),
'Classify the company name itself into exactly one mutually exclusive morphology. Use this precedence: (1) -ly if the name ends in ly as a productive startup suffix; (2) -ify if it ends in ify; (3) dropped vowel if a familiar word or phrase has one or more vowels deliberately removed; (4) portmanteau if recognizable word fragments are blended with overlap, clipping, or phonetic fusion; (5) compound if two or more intact recognizable words or morphemes are joined, spaced, hyphenated, or camel-cased; (6) real word if it is an existing dictionary word, proper name, place, mythological name, acronym, number, or ordinary phrase used without morphological alteration; (7) Latin coinage if it is a coined or adapted Latinate/Greek-classical formation rather than a normal modern word; (8) invented if it has no clear derivation. Brand styling and legal suffixes do not change the class.',
morphology := 'one label only: real word, compound, portmanteau, dropped vowel, -ly, -ify, Latin coinage, or invented',
rationale := 'brief morphological decomposition or reason'
) AS classification
FROM WEB_FETCH(
'https://45bwzj1sgc-dsn.algolia.net/1/indexes/YCCompany_production?x-algolia-application-id=45BWZJ1SGC&x-algolia-api-key=NzllNTY5MzJiZGM2OTY2ZTQwMDEzOTNhYWZiZGRjODlhYzVkNjBmOGRjNzJiMWM4ZTU0ZDlhYTZjOTJiMjlhMWFuYWx5dGljc1RhZ3M9eWNkYyZyZXN0cmljdEluZGljZXM9WUNDb21wYW55X3Byb2R1Y3Rpb24lMkNZQ0NvbXBhbnlfQnlfTGF1bmNoX0RhdGVfcHJvZHVjdGlvbiZ0YWdGaWx0ZXJzPSU1QiUyMnljZGNfcHVibGljJTIyJTVE&query=&hitsPerPage=1000&page=0&tagFilters=%5B%22ycdc_public%22%5D',
'https://45bwzj1sgc-dsn.algolia.net/1/indexes/YCCompany_production?x-algolia-application-id=45BWZJ1SGC&x-algolia-api-key=NzllNTY5MzJiZGM2OTY2ZTQwMDEzOTNhYWZiZGRjODlhYzVkNjBmOGRjNzJiMWM4ZTU0ZDlhYTZjOTJiMjlhMWFuYWx5dGljc1RhZ3M9eWNkYyZyZXN0cmljdEluZGljZXM9WUNDb21wYW55X3Byb2R1Y3Rpb24lMkNZQ0NvbXBhbnlfQnlfTGF1bmNoX0RhdGVfcHJvZHVjdGlvbiZ0YWdGaWx0ZXJzPSU1QiUyMnljZGNfcHVibGljJTIyJTVE&query=&hitsPerPage=1000&page=1&tagFilters=%5B%22ycdc_public%22%5D',
'https://45bwzj1sgc-dsn.algolia.net/1/indexes/YCCompany_production?x-algolia-application-id=45BWZJ1SGC&x-algolia-api-key=NzllNTY5MzJiZGM2OTY2ZTQwMDEzOTNhYWZiZGRjODlhYzVkNjBmOGRjNzJiMWM4ZTU0ZDlhYTZjOTJiMjlhMWFuYWx5dGljc1RhZ3M9eWNkYyZyZXN0cmljdEluZGljZXM9WUNDb21wYW55X3Byb2R1Y3Rpb24lMkNZQ0NvbXBhbnlfQnlfTGF1bmNoX0RhdGVfcHJvZHVjdGlvbiZ0YWdGaWx0ZXJzPSU1QiUyMnljZGNfcHVibGljJTIyJTVE&query=&hitsPerPage=1000&page=2&tagFilters=%5B%22ycdc_public%22%5D',
'https://45bwzj1sgc-dsn.algolia.net/1/indexes/YCCompany_production?x-algolia-application-id=45BWZJ1SGC&x-algolia-api-key=NzllNTY5MzJiZGM2OTY2ZTQwMDEzOTNhYWZiZGRjODlhYzVkNjBmOGRjNzJiMWM4ZTU0ZDlhYTZjOTJiMjlhMWFuYWx5dGljc1RhZ3M9eWNkYyZyZXN0cmljdEluZGljZXM9WUNDb21wYW55X3Byb2R1Y3Rpb24lMkNZQ0NvbXBhbnlfQnlfTGF1bmNoX0RhdGVfcHJvZHVjdGlvbiZ0YWdGaWx0ZXJzPSU1QiUyMnljZGNfcHVibGljJTIyJTVE&query=&hitsPerPage=1000&page=3&tagFilters=%5B%22ycdc_public%22%5D',
'https://45bwzj1sgc-dsn.algolia.net/1/indexes/YCCompany_production?x-algolia-application-id=45BWZJ1SGC&x-algolia-api-key=NzllNTY5MzJiZGM2OTY2ZTQwMDEzOTNhYWZiZGRjODlhYzVkNjBmOGRjNzJiMWM4ZTU0ZDlhYTZjOTJiMjlhMWFuYWx5dGljc1RhZ3M9eWNkYyZyZXN0cmljdEluZGljZXM9WUNDb21wYW55X3Byb2R1Y3Rpb24lMkNZQ0NvbXBhbnlfQnlfTGF1bmNoX0RhdGVfcHJvZHVjdGlvbiZ0YWdGaWx0ZXJzPSU1QiUyMnljZGNfcHVibGljJTIyJTVE&query=&hitsPerPage=1000&page=4&tagFilters=%5B%22ycdc_public%22%5D',
'https://45bwzj1sgc-dsn.algolia.net/1/indexes/YCCompany_production?x-algolia-application-id=45BWZJ1SGC&x-algolia-api-key=NzllNTY5MzJiZGM2OTY2ZTQwMDEzOTNhYWZiZGRjODlhYzVkNjBmOGRjNzJiMWM4ZTU0ZDlhYTZjOTJiMjlhMWFuYWx5dGljc1RhZ3M9eWNkYyZyZXN0cmljdEluZGljZXM9WUNDb21wYW55X3Byb2R1Y3Rpb24lMkNZQ0NvbXBhbnlfQnlfTGF1bmNoX0RhdGVfcHJvZHVjdGlvbiZ0YWdGaWx0ZXJzPSU1QiUyMnljZGNfcHVibGljJTIyJTVE&query=&hitsPerPage=1000&page=5&tagFilters=%5B%22ycdc_public%22%5D',
'https://45bwzj1sgc-dsn.algolia.net/1/indexes/YCCompany_production?x-algolia-application-id=45BWZJ1SGC&x-algolia-api-key=NzllNTY5MzJiZGM2OTY2ZTQwMDEzOTNhYWZiZGRjODlhYzVkNjBmOGRjNzJiMWM4ZTU0ZDlhYTZjOTJiMjlhMWFuYWx5dGljc1RhZ3M9eWNkYyZyZXN0cmljdEluZGljZXM9WUNDb21wYW55X3Byb2R1Y3Rpb24lMkNZQ0NvbXBhbnlfQnlfTGF1bmNoX0RhdGVfcHJvZHVjdGlvbiZ0YWdGaWx0ZXJzPSU1QiUyMnljZGNfcHVibGljJTIyJTVE&query=&hitsPerPage=1000&page=6&tagFilters=%5B%22ycdc_public%22%5D'
) AS f,
json_each(json_extract(TRY_CAST(f.content AS JSON), '$.hits')) AS j{
"show_preview": true
}result_set_id: r38c52a5968d
total_rows: 1000
columns[7]: batch,batch_year,classification,company_id,company_name,one_liner,source_url
preview:
sample_rows[3]:
- company_id: "30918"
company_name: ComplyDo
batch: Fall 2025
batch_year: 2025
one_liner: Global Compliance for Enterprises
source_url: "https://45bwzj1sgc-dsn.algolia.net/1/indexes/YCCompany_production?x-algolia-application-id=45BWZJ1SGC&x-algolia-api-key=NzllNTY5MzJiZGM2OTY2ZTQwMDEzOTNhYWZiZGRjODlhYzVkNjBmOGRjNzJiMWM4ZTU0ZDlhYTZjOTJiMjlhMWFuYWx5dGljc1RhZ3M9eWNkYyZyZXN0cmljdEluZGljZXM9WUNDb21wYW55X3Byb2R1Y3Rpb24lMkNZQ0NvbXBhbnlfQnlf... [truncated in preview]"
classification:
morphology: compound
rationale: ComplyDo is a compound of the intact recognizable words Comply and Do.
- company_id: "32131"
company_name: Shielded
batch: Summer 2026
batch_year: 2026
one_liner: The agentic origination desk for supply chain risk.
source_url: "https://45bwzj1sgc-dsn.algolia.net/1/indexes/YCCompany_production?x-algolia-application-id=45BWZJ1SGC&x-algolia-api-key=NzllNTY5MzJiZGM2OTY2ZTQwMDEzOTNhYWZiZGRjODlhYzVkNjBmOGRjNzJiMWM4ZTU0ZDlhYTZjOTJiMjlhMWFuYWx5dGljc1RhZ3M9eWNkYyZyZXN0cmljdEluZGljZXM9WUNDb21wYW55X3Byb2R1Y3Rpb24lMkNZQ0NvbXBhbnlfQnlf... [truncated in preview]"
classification:
morphology: real word
rationale: Shielded — The agentic origination desk for supply chain risk.
- company_id: "531"
company_name: DoorDash
batch: Summer 2013
batch_year: 2013
one_liner: Restaurant delivery.
source_url: "https://45bwzj1sgc-dsn.algolia.net/1/indexes/YCCompany_production?x-algolia-application-id=45BWZJ1SGC&x-algolia-api-key=NzllNTY5MzJiZGM2OTY2ZTQwMDEzOTNhYWZiZGRjODlhYzVkNjBmOGRjNzJiMWM4ZTU0ZDlhYTZjOTJiMjlhMWFuYWx5dGljc1RhZ3M9eWNkYyZyZXN0cmljdEluZGljZXM9WUNDb21wYW55X3Byb2R1Y3Rpb24lMkNZQ0NvbXBhbnlfQnlf... [truncated in preview]"
classification:
morphology: compound
rationale: "Door + Dash, two intact recognizable words joined together."
notes[2,]{text}:
"The full table is too large to return in one response. This is a preview, not the data — do not draw conclusions from it alone. Query the result set by id: SEM_EXTRACT fields, filter with exact WHERE, aggregate, or page with LIMIT/OFFSET. Even if the preview looks irrelevant, probe the full result set before searching again."
"Fetch result set: r4c460dc1c3d (7 rows), reference it as FROM r4c460dc1c3d in follow-up queries"result set r38c52a5968d · 1000 rows
| company_id | company_name | batch | batch_year | one_liner | source_url | classification |
|---|---|---|---|---|---|---|
| 30918 | ComplyDo | Fall 2025 | 2025 | Global Compliance for Enterprises | https://45bwzj1sgc-dsn.algolia.net/1/indexes/YCCompany_production?x-algolia-application-id=45BWZJ1SGC&x-algolia-api-key=NzllNTY5MzJiZGM2OTY2ZTQwMDEzOTNhYWZiZGR… | {"morphology": "compound", "rationale": "ComplyDo is a compound of the intact recognizable words Comply and Do."} |
| 30919 | Codyco | Fall 2025 | 2025 | The AI Reservation Team for Hotel Groups | https://45bwzj1sgc-dsn.algolia.net/1/indexes/YCCompany_production?x-algolia-application-id=45BWZJ1SGC&x-algolia-api-key=NzllNTY5MzJiZGM2OTY2ZTQwMDEzOTNhYWZiZGR… | {"morphology": "compound", "rationale": "Codyco — The AI Reservation Team for Hotel Groups is formed by joining the name or root Cody with co"} |
| 30920 | Lexi | Fall 2025 | 2025 | AI Operating System for Legal Teams | https://45bwzj1sgc-dsn.algolia.net/1/indexes/YCCompany_production?x-algolia-application-id=45BWZJ1SGC&x-algolia-api-key=NzllNTY5MzJiZGM2OTY2ZTQwMDEzOTNhYWZiZGR… | {"morphology": "real word", "rationale": "Lexi is a proper name and familiar short form associated with legal terminology or given names, used without morpholo… |
| 30921 | Compyle | Fall 2025 | 2025 | The coding agent that actually collaborates with you | https://45bwzj1sgc-dsn.algolia.net/1/indexes/YCCompany_production?x-algolia-application-id=45BWZJ1SGC&x-algolia-api-key=NzllNTY5MzJiZGM2OTY2ZTQwMDEzOTNhYWZiZGR… | {"morphology": "dropped vowel", "rationale": "Compyle — The name is a modification of the word compile with the vowel 'i' replaced by 'y'."} |
| 30922 | Selfin | Fall 2025 | 2025 | Building the first AI Bank | https://45bwzj1sgc-dsn.algolia.net/1/indexes/YCCompany_production?x-algolia-application-id=45BWZJ1SGC&x-algolia-api-key=NzllNTY5MzJiZGM2OTY2ZTQwMDEzOTNhYWZiZGR… | {"morphology": "portmanteau", "rationale": "Selfin blends 'Self' and 'Finance'."} |
tool call · select
SELECT
json_extract_string(j.value, '$.id') AS company_id,
json_extract_string(j.value, '$.name') AS company_name,
b.batch,
b.batch_year,
json_extract_string(j.value, '$.one_liner') AS one_liner,
f.url AS source_url,
SEM_EXTRACT(
json_extract_string(j.value, '$.name') || ' — ' || coalesce(json_extract_string(j.value, '$.one_liner'), ''),
'Classify the company name itself into exactly one mutually exclusive morphology using this precedence: suffix -ly; suffix -ify; dropped vowel from a familiar word or phrase; portmanteau blending recognizable fragments with clipping, overlap, or phonetic fusion; compound joining two or more intact recognizable words or morphemes; real word including dictionary words, ordinary phrases, proper names, places, mythological names, acronyms, and numbers used without morphological alteration; Latin coinage for coined or adapted Latinate or Greek-classical formations; invented when no clear derivation exists. Ignore brand styling and legal suffixes.',
morphology := 'one label only: real word, compound, portmanteau, dropped vowel, -ly, -ify, Latin coinage, or invented',
rationale := 'brief morphological decomposition or reason'
) AS classification
FROM (
SELECT
key AS batch,
TRY_CAST(regexp_extract(key, '(20[0-9]{2})', 1) AS INTEGER) AS batch_year,
'https://45bwzj1sgc-dsn.algolia.net/1/indexes/YCCompany_production?x-algolia-application-id=45BWZJ1SGC&x-algolia-api-key=NzllNTY5MzJiZGM2OTY2ZTQwMDEzOTNhYWZiZGRjODlhYzVkNjBmOGRjNzJiMWM4ZTU0ZDlhYTZjOTJiMjlhMWFuYWx5dGljc1RhZ3M9eWNkYyZyZXN0cmljdEluZGljZXM9WUNDb21wYW55X3Byb2R1Y3Rpb24lMkNZQ0NvbXBhbnlfQnlfTGF1bmNoX0RhdGVfcHJvZHVjdGlvbiZ0YWdGaWx0ZXJzPSU1QiUyMnljZGNfcHVibGljJTIyJTVE&query=&hitsPerPage=1000&page=0&tagFilters=%5B%22ycdc_public%22%5D&facetFilters=%5B%22batch%3A' || replace(key, ' ', '%20') || '%22%5D' AS fetch_url
FROM recb5c3b80cc,
json_each(json_extract(TRY_CAST(content AS JSON), '$.by_batch'))
WHERE url = 'https://api.exploreyc.com/api/stats'
AND TRY_CAST(regexp_extract(key, '(20[0-9]{2})', 1) AS INTEGER) BETWEEN 2005 AND 2026
) AS b,
UNNEST(WEB_FETCH(b.fetch_url), recursive := true) AS f,
json_each(json_extract(TRY_CAST(f.content AS JSON), '$.hits')) AS j{
"show_preview": true
}error: "SQL parse error: Invalid expression / Unexpected token. Line 24, Col: 6.\n '\n AND TRY_CAST(regexp_extract(key, '(20[0-9]{2})', 1) AS INTEGER) BETWEEN 2005 AND 2026\n) AS b,\n[4mUNNEST[0m(WEB_FETCH(b.fetch_url), recursive := true) AS f,\njson_each(json_extract(TRY_CAST(f.content AS JSON)"tool call · select
SELECT
json_extract_string(j.value, '$.id') AS company_id,
json_extract_string(j.value, '$.name') AS company_name,
x.batch,
x.batch_year,
json_extract_string(j.value, '$.one_liner') AS one_liner,
x.url AS source_url,
SEM_EXTRACT(
json_extract_string(j.value, '$.name') || ' — ' || coalesce(json_extract_string(j.value, '$.one_liner'), ''),
'Classify the company name itself into exactly one mutually exclusive morphology using this precedence: suffix -ly; suffix -ify; dropped vowel from a familiar word or phrase; portmanteau blending recognizable fragments with clipping, overlap, or phonetic fusion; compound joining two or more intact recognizable words or morphemes; real word including dictionary words, ordinary phrases, proper names, places, mythological names, acronyms, and numbers used without morphological alteration; Latin coinage for coined or adapted Latinate or Greek-classical formations; invented when no clear derivation exists. Ignore brand styling and legal suffixes.',
morphology := 'one label only: real word, compound, portmanteau, dropped vowel, -ly, -ify, Latin coinage, or invented',
rationale := 'brief morphological decomposition or reason'
) AS classification
FROM (
SELECT
batch,
batch_year,
UNNEST(WEB_FETCH(fetch_url), recursive := true)
FROM (
SELECT
key AS batch,
TRY_CAST(regexp_extract(key, '(20[0-9]{2})', 1) AS INTEGER) AS batch_year,
'https://45bwzj1sgc-dsn.algolia.net/1/indexes/YCCompany_production?x-algolia-application-id=45BWZJ1SGC&x-algolia-api-key=NzllNTY5MzJiZGM2OTY2ZTQwMDEzOTNhYWZiZGRjODlhYzVkNjBmOGRjNzJiMWM4ZTU0ZDlhYTZjOTJiMjlhMWFuYWx5dGljc1RhZ3M9eWNkYyZyZXN0cmljdEluZGljZXM9WUNDb21wYW55X3Byb2R1Y3Rpb24lMkNZQ0NvbXBhbnlfQnlfTGF1bmNoX0RhdGVfcHJvZHVjdGlvbiZ0YWdGaWx0ZXJzPSU1QiUyMnljZGNfcHVibGljJTIyJTVE&query=&hitsPerPage=1000&page=0&tagFilters=%5B%22ycdc_public%22%5D&facetFilters=%5B%22batch%3A' || replace(key, ' ', '%20') || '%22%5D' AS fetch_url
FROM recb5c3b80cc,
json_each(json_extract(TRY_CAST(content AS JSON), '$.by_batch'))
WHERE url = 'https://api.exploreyc.com/api/stats'
AND TRY_CAST(regexp_extract(key, '(20[0-9]{2})', 1) AS INTEGER) BETWEEN 2005 AND 2026
)
) AS x,
json_each(json_extract(TRY_CAST(x.content AS JSON), '$.hits')) AS j{
"show_preview": true
}error: "SQL execution failed: Invalid Input Error: Arrow Appender: The maximum total string size for regular string buffers is 2147483647 but the offset of 2148351714 exceeds this.\n* SET arrow_large_buffer_size=true to use large string buffers\nTables in this query: recb5c3b80cc(content, published_at, title, url)"
notes[1,]{text}:
Per-row fetched pages saved as result set r65e7e85cf84 (48 rows) — query FROM r65e7e85cf84 to re-extract from the same pages; do NOT re-fetch.tool call · select
SELECT
json_extract_string(j.value, '$.id') AS company_id,
json_extract_string(j.value, '$.name') AS company_name,
x.batch,
x.batch_year,
json_extract_string(j.value, '$.one_liner') AS one_liner,
x.url AS source_url,
SEM_EXTRACT(
json_extract_string(j.value, '$.name') || ' — ' || coalesce(json_extract_string(j.value, '$.one_liner'), ''),
'Classify the company name itself into exactly one mutually exclusive morphology using this precedence: suffix -ly; suffix -ify; dropped vowel from a familiar word or phrase; portmanteau blending recognizable fragments with clipping, overlap, or phonetic fusion; compound joining two or more intact recognizable words or morphemes; real word including dictionary words, ordinary phrases, proper names, places, mythological names, acronyms, and numbers used without morphological alteration; Latin coinage for coined or adapted Latinate or Greek-classical formations; invented when no clear derivation exists. Ignore brand styling and legal suffixes.',
morphology := 'one label only: real word, compound, portmanteau, dropped vowel, -ly, -ify, Latin coinage, or invented',
rationale := 'brief morphological decomposition or reason'
) AS classification
FROM (
SELECT
url,
content,
regexp_replace(regexp_extract(url, 'batch%3A([^%]+(?:%20[^%]+)*)%22', 1), '%20', ' ', 'g') AS batch,
TRY_CAST(regexp_extract(url, 'batch%3A[^%]*(20[0-9]{2})', 1) AS INTEGER) AS batch_year
FROM r65e7e85cf84
) AS x,
json_each(json_extract(TRY_CAST(x.content AS JSON), '$.hits')) AS j{
"show_preview": true
}result_set_id: r9f46ac3a2ef
total_rows: 2000
columns[7]: batch,batch_year,classification,company_id,company_name,one_liner,source_url
preview:
sample_rows[3]:
- company_id: "368"
company_name: Snipshot
batch: Winter 2006
batch_year: null
one_liner: We sold Snipshot to Ansa in 2013.
source_url: "https://45bwzj1sgc-dsn.algolia.net/1/indexes/YCCompany_production?x-algolia-application-id=45BWZJ1SGC&x-algolia-api-key=NzllNTY5MzJiZGM2OTY2ZTQwMDEzOTNhYWZiZGRjODlhYzVkNjBmOGRjNzJiMWM4ZTU0ZDlhYTZjOTJiMjlhMWFuYWx5dGljc1RhZ3M9eWNkYyZyZXN0cmljdEluZGljZXM9WUNDb21wYW55X3Byb2R1Y3Rpb24lMkNZQ0NvbXBhbnlfQnlf... [truncated in preview]"
classification:
morphology: compound
rationale: "Snipshot is formed by joining two intact recognizable words: snip and shot."
- company_id: "35784"
company_name: Vorelios
batch: Fall 2026
batch_year: null
one_liner: AI that simulates physics 1000x faster
source_url: "https://45bwzj1sgc-dsn.algolia.net/1/indexes/YCCompany_production?x-algolia-application-id=45BWZJ1SGC&x-algolia-api-key=NzllNTY5MzJiZGM2OTY2ZTQwMDEzOTNhYWZiZGRjODlhYzVkNjBmOGRjNzJiMWM4ZTU0ZDlhYTZjOTJiMjlhMWFuYWx5dGljc1RhZ3M9eWNkYyZyZXN0cmljdEluZGljZXM9WUNDb21wYW55X3Byb2R1Y3Rpb24lMkNZQ0NvbXBhbnlfQnlf... [truncated in preview]"
classification:
morphology: Latin coinage
rationale: Vorelios uses Latin-sounding roots and formations (-ios suffix) without direct dictionary status.
- company_id: "30068"
company_name: Stewdio
batch: Fall 2024
batch_year: null
one_liner: All-in-one AI creative platform
source_url: "https://45bwzj1sgc-dsn.algolia.net/1/indexes/YCCompany_production?x-algolia-application-id=45BWZJ1SGC&x-algolia-api-key=NzllNTY5MzJiZGM2OTY2ZTQwMDEzOTNhYWZiZGRjODlhYzVkNjBmOGRjNzJiMWM4ZTU0ZDlhYTZjOTJiMjlhMWFuYWx5dGljc1RhZ3M9eWNkYyZyZXN0cmljdEluZGljZXM9WUNDb21wYW55X3Byb2R1Y3Rpb24lMkNZQ0NvbXBhbnlfQnlf... [truncated in preview]"
classification:
morphology: portmanteau
rationale: Stewdio is a portmanteau combining Stew and Studio.
notes[2,]{text}:
"The full table is too large to return in one response. This is a preview, not the data — do not draw conclusions from it alone. Query the result set by id: SEM_EXTRACT fields, filter with exact WHERE, aggregate, or page with LIMIT/OFFSET. Even if the preview looks irrelevant, probe the full result set before searching again."
Semantic operators ran on only the first 2000 of 6199 input rows; the rest are absent from this result. Narrow the input with exact filters or process it in batches.result set r9f46ac3a2ef · 2000 rows
| company_id | company_name | batch | batch_year | one_liner | source_url | classification |
|---|---|---|---|---|---|---|
| 368 | Snipshot | Winter 2006 | null | We sold Snipshot to Ansa in 2013. | https://45bwzj1sgc-dsn.algolia.net/1/indexes/YCCompany_production?x-algolia-application-id=45BWZJ1SGC&x-algolia-api-key=NzllNTY5MzJiZGM2OTY2ZTQwMDEzOTNhYWZiZGR… | {"morphology": "compound", "rationale": "Snipshot is formed by joining two intact recognizable words: snip and shot."} |
| 376 | Simmery | Summer 2005 | null | https://45bwzj1sgc-dsn.algolia.net/1/indexes/YCCompany_production?x-algolia-application-id=45BWZJ1SGC&x-algolia-api-key=NzllNTY5MzJiZGM2OTY2ZTQwMDEzOTNhYWZiZGR… | {"morphology": "invented", "rationale": "Simmery is an invented term with no clear dictionary derivation."} | |
| 358 | Jamglue | Summer 2006 | null | https://45bwzj1sgc-dsn.algolia.net/1/indexes/YCCompany_production?x-algolia-application-id=45BWZJ1SGC&x-algolia-api-key=NzllNTY5MzJiZGM2OTY2ZTQwMDEzOTNhYWZiZGR… | {"morphology": "compound", "rationale": "Jamglue is a compound of two intact recognizable words: jam and glue."} | |
| 345 | Tsumobi | Winter 2007 | null | Messaging. | https://45bwzj1sgc-dsn.algolia.net/1/indexes/YCCompany_production?x-algolia-application-id=45BWZJ1SGC&x-algolia-api-key=NzllNTY5MzJiZGM2OTY2ZTQwMDEzOTNhYWZiZGR… | {"morphology": "invented", "rationale": "Tsumobi is an invented word with no clear derivation from English morphemes or recognized roots."} |
| 265 | Recurious | Winter 2009 | null | https://45bwzj1sgc-dsn.algolia.net/1/indexes/YCCompany_production?x-algolia-application-id=45BWZJ1SGC&x-algolia-api-key=NzllNTY5MzJiZGM2OTY2ZTQwMDEzOTNhYWZiZGR… | {"morphology": "portmanteau", "rationale": "Blend of the words 're-' and 'curious'."} |
tool call · select
SELECT
url AS source_url,
UNNEST(
SEM_EXTRACT_ALL(
content,
'a Y Combinator company record in this Algolia JSON response; return every company. Classify the company name itself into exactly one mutually exclusive morphology using this precedence: suffix -ly; suffix -ify; dropped vowel from a familiar word or phrase; portmanteau blending recognizable fragments with clipping, overlap, or phonetic fusion; compound joining two or more intact recognizable words or morphemes; real word including dictionary words, ordinary phrases, proper names, places, mythological names, acronyms, and numbers used without morphological alteration; Latin coinage for coined or adapted Latinate or Greek-classical formations; invented when no clear derivation exists. Ignore brand styling and legal suffixes.',
company_id := 'company id',
company_name := 'company name',
batch := 'YC batch name',
morphology := 'one label only: real word, compound, portmanteau, dropped vowel, -ly, -ify, Latin coinage, or invented',
rationale := 'brief morphological decomposition or reason'
),
recursive := true
)
FROM r65e7e85cf84{
"show_preview": true
}result_set_id: r04d4d687a7d
total_rows: 1159
columns[6]: batch,company_id,company_name,morphology,rationale,source_url
preview:
sample_rows[3,]{source_url,company_id,company_name,batch,morphology,rationale}:
"https://45bwzj1sgc-dsn.algolia.net/1/indexes/YCCompany_production?x-algolia-application-id=45BWZJ1SGC&x-algolia-api-key=NzllNTY5MzJiZGM2OTY2ZTQwMDEzOTNhYWZiZGRjODlhYzVkNjBmOGRjNzJiMWM4ZTU0ZDlhYTZjOTJiMjlhMWFuYWx5dGljc1RhZ3M9eWNkYyZyZXN0cmljdEluZGljZXM9WUNDb21wYW55X3Byb2R1Y3Rpb24lMkNZQ0NvbXBhbnlfQnlf... [truncated in preview]",370,Wufoo,null,invented,"Coined brand name with no clear dictionary derivation or standard compounding/portmanteau structure beyond phonetic styling resembling 'woof'.-like sound or playful naming without clear word parts... [truncated in preview]"
"https://45bwzj1sgc-dsn.algolia.net/1/indexes/YCCompany_production?x-algolia-application-id=45BWZJ1SGC&x-algolia-api-key=NzllNTY5MzJiZGM2OTY2ZTQwMDEzOTNhYWZiZGRjODlhYzVkNjBmOGRjNzJiMWM4ZTU0ZDlhYTZjOTJiMjlhMWFuYWx5dGljc1RhZ3M9eWNkYyZyZXN0cmljdEluZGljZXM9WUNDb21wYW55X3Byb2R1Y3Rpb24lMkNZQ0NvbXBhbnlfQnlf... [truncated in preview]",999,Focal Systems,null,compound,"joining two intact words: focal and systems"
"https://45bwzj1sgc-dsn.algolia.net/1/indexes/YCCompany_production?x-algolia-application-id=45BWZJ1SGC&x-algolia-api-key=NzllNTY5MzJiZGM2OTY2ZTQwMDEzOTNhYWZiZGRjODlhYzVkNjBmOGRjNzJiMWM4ZTU0ZDlhYTZjOTJiMjlhMWFuYWx5dGljc1RhZ3M9eWNkYyZyZXN0cmljdEluZGljZXM9WUNDb21wYW55X3Byb2R1Y3Rpb24lMkNZQ0NvbXBhbnlfQnlf... [truncated in preview]",26134,Beyond Aero,null,compound,Joining two intact recognizable words ('beyond' and 'aero').
notes[1,]{text}:
"The full table is too large to return in one response. This is a preview, not the data — do not draw conclusions from it alone. Query the result set by id: SEM_EXTRACT fields, filter with exact WHERE, aggregate, or page with LIMIT/OFFSET. Even if the preview looks irrelevant, probe the full result set before searching again."result set r04d4d687a7d · 1159 rows
| source_url | company_id | company_name | batch | morphology | rationale |
|---|---|---|---|---|---|
| https://45bwzj1sgc-dsn.algolia.net/1/indexes/YCCompany_production?x-algolia-application-id=45BWZJ1SGC&x-algolia-api-key=NzllNTY5MzJiZGM2OTY2ZTQwMDEzOTNhYWZiZGR… | 370 | Wufoo | null | invented | Coined brand name with no clear dictionary derivation or standard compounding/portmanteau structure beyond phonetic styling resembling 'woof'.-like sound or pl… |
| https://45bwzj1sgc-dsn.algolia.net/1/indexes/YCCompany_production?x-algolia-application-id=45BWZJ1SGC&x-algolia-api-key=NzllNTY5MzJiZGM2OTY2ZTQwMDEzOTNhYWZiZGR… | 369 | Project Wedding | null | compound | Joining two intact recognizable words: 'Project' and 'Wedding'. |
| https://45bwzj1sgc-dsn.algolia.net/1/indexes/YCCompany_production?x-algolia-application-id=45BWZJ1SGC&x-algolia-api-key=NzllNTY5MzJiZGM2OTY2ZTQwMDEzOTNhYWZiZGR… | 367 | Clustrix | null | portmanteau | Blending recognizable fragments: 'Cluster' and 'Relational / RDBMS / index' or 'SQL/Matrix', specifically combining 'Cluster' and 'Matrix'/'Index' with phoneti… |
| https://45bwzj1sgc-dsn.algolia.net/1/indexes/YCCompany_production?x-algolia-application-id=45BWZJ1SGC&x-algolia-api-key=NzllNTY5MzJiZGM2OTY2ZTQwMDEzOTNhYWZiZGR… | 366 | Inkling | null | real word | A standard dictionary word ('inkling' meaning a slight knowledge or suspicion). |
| https://45bwzj1sgc-dsn.algolia.net/1/indexes/YCCompany_production?x-algolia-application-id=45BWZJ1SGC&x-algolia-api-key=NzllNTY5MzJiZGM2OTY2ZTQwMDEzOTNhYWZiZGR… | 372 | Audiobeta | null | compound | Joining two intact recognizable words or morphemes: 'Audio' and 'Beta'. |
tool call · generate_html_report
{"brief":"The user asked verbatim: “How has the shape of startup names changed in 20 years? Classify all Y Combinator companies by name morphology — real word, compound, portmanteau, dropped vowel, -ly, -ify, Latin coinage, invented — and plot the mix by batch year.”\n\nBuild a beautiful editorial data story about YC naming morphology. Lead with a large visual that uses the company-level rows as a dense 100% mosaic/tile plot: columns are batch years parsed from the `batch` strings, tiles are individual companies, and color is `classification.morphology`. This form can show the mix directly without inventing aggregate percentages. Follow with a batch-season view and a searchable audit table of company name, batch, morphology, and rationale. Use the eight requested categories in a consistent legend and preserve this precedence used in classification: -ly; -ify; dropped vowel; portmanteau; compound; real word; Latin coinage; invented.\n\nImportant limitation: do NOT present this as a completed census. The live YC directory/ExploreYC snapshot reports 6,217 companies total (6,199 records were fetched across 48 named 2005–2026 batch pages), but the semantic classification engine returned only the first 2,000 company rows in this run. Say prominently that this is a 2,000-company partial classification and that the mix is descriptive of the processed sample, not definitive for all YC. The original request was for all companies, but compute limits prevented full classification; transparency matters more than overclaiming.\n\nThe defensible qualitative reading to emphasize cautiously is whether the visual supports a broad transition from conspicuously engineered Web-2.0-style forms (blends, vowel deletion, -ly/-ify) toward plainer real-word and transparent compound names in recent cohorts. Treat that as a visual hypothesis, not a proven percentage result, because no aggregate table was produced.\n\nResult set r9f46ac3a2ef is the main data: 2,000 company-level rows with company_id, company_name, YC batch, one-liner, morphology classification and rationale, plus the live Algolia source URL. Parse the four-digit year from `batch`; ignore its null `batch_year` helper field. Nested `classification.morphology` and `classification.rationale` are the label and audit note. Make source links unobtrusive and do not print the long Algolia URLs. Include concise methodology and ambiguity notes: classes are mutually exclusive; compounds use intact elements, portmanteaus use clipped/overlapping elements, and invented is the residual class. Classification is model-assisted and should be treated as reproducible coding rather than ground truth.\n\nMake the report visually striking, publication-quality, and honest about sample coverage.","result_set_ids":["r9f46ac3a2ef"],"title":"Twenty Years of YC Startup-Name Morphology","prev_report_token":null}