Skip to content

0012 — Company boards are identified by board_ref, not a single slug

Status: accepted

The company list identifies each board with a board_ref: the ATS-specific path fragment an adapter needs to reach one company's postings. For Greenhouse and Lever that is a bare token (boards.greenhouse.io/<ref>), but several target ATS need more than one parameter — Workday boards, for example, live at {tenant}.{instance}.myworkdayjobs.com/wday/cxs/{tenant}/{site}/jobs. A one-token company_slug column cannot express that, and V2 explicitly plans more sources.

So the contract is: board_ref is opaque to the pipeline and interpreted by the adapter that owns the source. Greenhouse/Lever adapters format it straight into their URL template; a future Workday adapter splits tenant/instance/site itself. URL templates live only in the source registry (ingest/sources.py); adapters are constructed from it, so there is a single source of truth.

The legacy company_slug CSV header is accepted as a validation alias so existing private lists (including the GitHub Actions variable) keep working unchanged.

Validation is adapter-owned too. Because the ref's shape is source-specific, so is its format check: each source in ingest/sources.py carries a validate_board_ref (default: a bare board token — no slashes, spaces, or URL — which a multi-segment ATS like Workday overrides). load_companies runs it on every active row before any fetch, so a pasted URL or a stray slash fails loudly at load time instead of building a broken request and being silently skipped as a 404. CSV parsing still passes multi-segment refs through untouched (splitting is the adapter's job, not the loader's).