# Y Combinator Jobs — Enriched & Normalized (`nomad-agent/ycombinator-enrich-translate-normalize-scraper`) Actor

Extract public Work at a Startup openings into nomad-agent-job-v1. Complete Inertia details provide descriptions, salary, equity, visa and company facts; optional owner-managed AI fills only missing description-backed fields.

- **URL**: https://apify.com/nomad-agent/ycombinator-enrich-translate-normalize-scraper.md
- **Developed by:** [Nomad Dev](https://apify.com/nomad-agent) (community)
- **Categories:** Jobs
- **Stats:** 2 total users, 1 monthly users, 33.3% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.90 / 1,000 y combinator job results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Y Combinator Work at a Startup — normalized job Actor

This Actor reads complete YC Work at a Startup jobs from an owner-maintained
durable PostgreSQL inventory and emits strict `nomad-agent-job-v1` records. It
is the normalized successor to the legacy flat
`ycombinator-was-scraper`; it deliberately retains
`identity.source: "ycombinator_was"` so downstream job keys remain stable.

This public Actor never calls Work at a Startup. An owner-only scheduled
`ycombinator-private-collector` performs fixed-scope discovery and identity-
bound detail retrieval, then stores complete normalized records in the Netcup
position inventory. Public runs fail closed if that inventory is unavailable
or stale; there is no live-source fallback. The public host owns user filters,
optional null-only enrichment, selected-field translation, transactional
delivery inventory, PPE billing, delivery, analytics, and `RUN-SUMMARY`.

### Source and completeness boundary

Work at a Startup exposes a public JSON search endpoint and public Inertia job
pages. The private collector's hourly task fans out over 62 fixed ordered terms,
deduplicates by source job ID, and durably queues new, changed, or refresh-due
cards. A separate detail task writes only recognized, complete, identity-bound
jobs. Neither collector mode performs enrichment or translation.

A search card is never exposed by the public Actor. Every inventory row has a
recognized detail payload whose embedded job ID matches the requested URL and
whose `descriptionHtml` produces a non-empty complete description. The public
Actor scans a frozen inventory cursor and applies keyword, location, YC
taxonomy, normalized, work-arrangement, and unseen filters locally.

YC's session-bound authentication links contain regenerated application codes
and placeholder emails. They are neither used for candidate change detection
nor stored as durable application URLs; when no stable direct apply URL is
published, `data.application.url` points to the stable YC job page.

### Input

The required shared contract marker is:

```json
{
  "schemaVersion": "nomad-agent-job-search-input-v1",
  "keyword": "founding engineer",
  "postedWithin": "24h",
  "maxItems": 25
}
```

`keyword` is matched locally against cached normalized content. When blank, no
keyword filter is applied. Multiple local search terms and YC taxonomy filters
use the separately versioned extension:

```json
{
  "schemaVersion": "nomad-agent-job-search-input-v1",
  "postedWithin": "7d",
  "ycSearch": {
    "schemaVersion": "nomad-agent-ycombinator-search-v1",
    "queries": ["python", "founding engineer"],
    "companyBatches": ["S23", "W24"],
    "roleTypes": ["engineering"],
    "jobTypes": ["Full-time"]
  }
}
```

A non-empty `keyword` and non-empty `ycSearch.queries` are mutually exclusive.
The `queries` array is an OR-list over cached content; unlike LinkedIn search
partitions, it does not create separate upstream requests and its order has no
meaning. `companyBatches` matches YC accelerator cohorts such as `W24`,
`roleTypes` matches substrings in YC's published role category, and `jobTypes`
matches YC's published job-type label with common full-time, part-time,
contract, and internship aliases normalized.

YC exposes no source posting timestamp. The owner inventory assigns
`data.application.postedAt` once, at the UTC hour when it first observes a new
job, and preserves that timestamp across detail refreshes. `postedWithin`
supports `1h`, `24h`, `7d`, `30d`, and `any` against that cache-admission time;
the omitted default is `any`, so a first search is not silently restricted to
jobs first observed during the last day.
`companyLastActiveAt` remains separate company metadata. Top-level `orderBy`
accepts `newest` or `oldest` and uses the same cache-admission hour, with
unknown times last. `maxItems` accepts up to 1,000; `0` means the bounded
1,000-result window, never unlimited.

Other shared inputs are `location`, `workArrangements`, `dedupe`, `filters`,
`aiEnrichment`, `translateToEnglish`, `includeRaw`, and `analyticsEnabled`.
`firstRunMode` is an explicit opt-in to the same five-result paid evaluation
preset as LinkedIn while
preserving all search, taxonomy, ordering, and normalized-filter choices.
Owner policy such as inventory freshness, query bounds, models, and credentials
lives in `scraper.yaml`, not caller input.

### Output semantics

Every dataset row has exactly these roots:

```text
schemaVersion, identity, data, custom, llm, raw
```

Static source facts win. Optional AI enrichment may fill only still-null
description-backed normalized paths. Translation touches only configured short
display fields. Raw job description text and HTML remain unchanged unless the
caller sets `includeRaw: false`, which produces `raw: null` after processing.

Important source rules:

- `null` means unknown, unavailable, or not safely parsed.
- `[]` means the complete detail explicitly supplied an empty collection.
- `Remote (...)` is an explicit remote signal; a physical location does not
  prove onsite or hybrid.
- A dollar symbol alone does not prove USD, and an amount without period text
  does not prove annual compensation.
- One explicit salary amount maps to `compensation.exact`; a range maps to
  `minimum`/`maximum`.
- Founders remain company metadata and never become `hiringContacts`.
- YC `roleType` remains a job function/custom role label, not seniority.
- Skills remain source tags, with provenance in `custom.data.skills`. When no
  stronger typed preference already exists, the Actor also mirrors non-empty
  tags to `data.requirements.preferredSkills` as positive-only ranking context.
  It never asserts these generic tags as `requiredSkills`.
- Cleaned interview-process text maps to `data.application.selectionProcess`;
  `custom.data.interviewProcessHtml` preserves the YC-authored rich text because
  the canonical application contract has no HTML sibling.

The versioned custom contract is
[`../job_custom_schemas/ycombinator-v2.schema.json`](../job_custom_schemas/ycombinator-v2.schema.json).
It retains batch, slug, source location/job-type labels, raw equity/visa labels,
source skills, interview HTML, bounded opaque structured custom questions,
company hiring/technology descriptions, team size, last-active metadata, and
founder profiles. Role category, salary text, and interview-process text live
only in their canonical `data` fields.

YC company identity is already canonical where the shared contract has an
exact destination: name, numeric YC source ID, website, logo, and industry map
to `data.company`/`data.classification`. The remaining `custom.data.company`
facts are company-wide metadata—tagline, headquarters, team size, activity,
hiring/technology prose, and founders—with no safe v1 canonical destination.
The private collector already reads the complete YC company block from each
detail page, so LinkedIn's optional `companyProfileEnrichment` fetch is neither
needed nor exposed. LinkedIn's `companyFilters` also remain source-specific:
they filter facts from that optional LinkedIn company-page fetch, whereas
shared `filters` operate only on canonical normalized fields.

### Run summary and retries

The Actor writes the closed, minimal `nomad-agent-run-summary-v4` contract under
`RUN-SUMMARY`. It contains `status`, `startedAt`, `finishedAt`,
`resultsLimited`, `delivered`, and one bounded `retry` recommendation. It never
automatically starts another paid run and never falls back to YC when inventory
access fails. A deadline or summary-persistence failure fails the run; it cannot
be published as a successful empty result. The reviewed stage reserves leave
search time at a 600-second caller cap even when enrichment is enabled.

### Local verification

From the repository root:

```bash
apify/.venv/bin/python \
  apify/ycombinator-enrich-translate-normalize-scraper/scripts/verify_core_wheel.py
cd apify/ycombinator-enrich-translate-normalize-scraper
PYTHONPATH=wheels/nomad_job_scraper_core-1.1.31-py3-none-any.whl \
  ../.venv/bin/python -m pytest -q
PYTHONPATH=wheels/nomad_job_scraper_core-1.1.31-py3-none-any.whl \
  ../.venv/bin/python \
  scripts/generate_dataset_schema.py --check
```

After exporting one 600-second AI-enabled, translation-disabled canary as an
object containing `run`, `input`, `items`, and `runSummary`, verify terminal
classification plus complete text/HTML length and SHA-256 receipts with:

```bash
python scripts/verify_canary_evidence.py canary-evidence.json
```

Local tests and schema checks do not authorize an Actor build, push, pricing
change, deployment, or Store publication.

# Actor input Schema

## `firstRunMode` (type: `boolean`):

Run a small enriched evaluation without changing search or filter fields. When enabled, the Actor caps effective maxItems at 5, uses Silver AI enrichment, translates supported fields to English, disables cross-run deduplication, returns raw as null, and keeps anonymous analytics off. AI enrichment and translation are paid events.

## `schemaVersion` (type: `string`):

Version of the shared normalized job-search Actor input contract.

## `keyword` (type: `string`):

One search term. Cannot be combined with non-empty ycSearch.queries.

## `location` (type: `string`):

Case-insensitive match against YC's published location label.

## `workArrangements` (type: `array`):

Optional union of published workplace modes. YC labels establish remote explicitly; a physical place or missing workplace label is never treated as onsite or hybrid evidence.

## `postedWithin` (type: `string`):

Filter by the UTC hour when the owner inventory first observed the job. YC does not publish a source posting timestamp.

## `orderBy` (type: `string`):

Order matching results by the inventory first-observed hour. YC job ID and canonical URL break ties consistently.

## `maxItems` (type: `integer`):

Maximum number of complete normalized jobs to return. Set 0 to request the complete bounded delivery window of 1,000; it never means unlimited.

## `ycSearch` (type: `object`):

Optional versioned content-term and YC taxonomy filters applied locally to the cached normalized inventory. Leave queries empty to use keyword or no term filter.

## `dedupe` (type: `object`):

Enabled by default. An empty key derives isolated history from this Apify user and the resolved YC query and filter scope. Provide a distinct opaque alert/profile key only when searches should intentionally share history.

## `filters` (type: `object`):

Optional nomad-agent-job-filter-v1 expression over allowlisted normalized fields. Unknown rich facts never count as an early card rejection.

## `aiEnrichment` (type: `object`):

Reads only a complete public plain-text description and fills normalized fields that deterministic YC parsing left null. Static facts always win, and no customer API key or model is requested. Provider failure leaves the base result unchanged and is recorded in llm status.

## `translateToEnglish` (type: `boolean`):

After normalization, filtering, and optional enrichment, translate only configured short display fields with the Actor owner's DeepL account. Raw descriptions, HTML, company names, taxonomy, and provenance remain unchanged.

## `includeRaw` (type: `boolean`):

When enabled, return the complete YC detail description text and HTML in raw. Disable it to return raw: null after all requested processing.

## `analyticsEnabled` (type: `boolean`):

Opt in to one fail-open operational event with coarse run health and counts. It excludes caller IDs, search values, records, URLs, errors, secrets, source text, translations, and extracted values.

## Actor input object example

```json
{
  "firstRunMode": false,
  "schemaVersion": "nomad-agent-job-search-input-v1",
  "postedWithin": "any",
  "orderBy": "newest",
  "maxItems": 100,
  "dedupe": {
    "enabled": true,
    "key": ""
  },
  "aiEnrichment": {
    "enabled": false,
    "accuracy": "silver"
  },
  "translateToEnglish": false,
  "includeRaw": true,
  "analyticsEnabled": false
}
```

# Actor output Schema

## `dataset` (type: `string`):

No description

## `runSummary` (type: `string`):

Minimal nomad-agent-run-summary-v4 outcome with delivered count and one optional bounded retry recommendation. Only public outcome fields are included.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {};

// Run the Actor and wait for it to finish
const run = await client.actor("nomad-agent/ycombinator-enrich-translate-normalize-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {}

# Run the Actor and wait for it to finish
run = client.actor("nomad-agent/ycombinator-enrich-translate-normalize-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{}' |
apify call nomad-agent/ycombinator-enrich-translate-normalize-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,nomad-agent/ycombinator-enrich-translate-normalize-scraper"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/2YcCZK4Hsuxs5jU8L/builds/rHO2yvEg6AAyvb2sT/openapi.json
