# Providers (/docs/providers)



OpenOpps v0.1 separates aggregate source adapters from job-capable provider adapters. Source adapters discover company boards; provider adapters detect or fetch postings from public board providers through the CLI and local SQLite state.

Python plugins can contribute additional source adapters and job providers through the `openopps.plugins` entry point group. Plugin capabilities appear in `admin providers list` and registry surfaces when loaded successfully, and load failures, disabled entries, allow-list filters, and conflicts are visible in `plugins list`. Installed plugins are discovered by default but only executed when their entry-point names are listed in `OPENOPPS_PLUGIN_ALLOWED`, unless `OPENOPPS_PLUGIN_AUTOLOAD=true` is set.

Plugin job providers implement `BoardJobProvider` and return `openopps.providers.JobFetchResult` from `fetch_jobs`. Mark a result `authoritative=True` only after a complete, verified traversal: authoritative snapshots may close jobs that were not returned, while partial and plain-list results are rejected fail closed. The minimal plugin template demonstrates a non-authoritative no-op provider.

Use `examples/plugins/minimal-openopps-plugin/` as the starting template for a packaged plugin with a `pyproject.toml` entry point.

## Source Adapter Inventory [#source-adapter-inventory]

<SourceAdapterSummary />

## Source Catalog Sample [#source-catalog-sample]

<SourceCatalogSummary />

## Non-VC Source Families [#non-vc-source-families]

OpenOpps packages a small set of low-friction non-VC source families as source adapters, not job providers. These sources add company candidates and provenance metadata; they only become job-yielding after route detection, route probing, and provider job syncs find public job-capable routes.

| Source family                 | Packaged keys         | Notes                                                                                                      |
| ----------------------------- | --------------------- | ---------------------------------------------------------------------------------------------------------- |
| Official public-company index | `sec-company-tickers` | Included listed-company backbone; SEC fair-access controls can reject generic scheduled sync environments. |
| Public index CSV              | `sp500`, `nasdaq100`  | Included seed data for index membership; `nasdaq100` remains manual until a reviewed CSV is configured.    |
| Employer ranking CSV          | `fortune500`          | Included ranking seed data; supports embedded user-supplied CSV rows for reviewed local refreshes.         |
| Ecosystem landscape           | `cncf-landscape`      | Reads CNCF `landscape.yml` public fields and intentionally excludes logos and Crunchbase-derived fields.   |

Each packaged source may carry taxonomy metadata in `raw_metadata`. Discovery promotion requires the eight required fields below as non-empty strings; packaged catalog values are not a promotion grant.

## Required Discovery Taxonomy [#required-discovery-taxonomy]

Discovery candidates use `CandidateTaxonomy` (`src/openopps/discovery/data/candidate-taxonomy.schema.json`) and `REQUIRED_TAXONOMY_FIELDS` in `src/openopps/discovery/identity.py`. `validate_taxonomy` requires all eight required fields as non-empty strings. `sourceYear` is optional (four-digit integer 1900–9999 when present). Unknown field names fail closed.

| Field               | Required |
| ------------------- | -------- |
| `providerType`      | yes      |
| `coverageMode`      | yes      |
| `accessType`        | yes      |
| `licenseStatus`     | yes      |
| `refreshCadence`    | yes      |
| `sourceCategory`    | yes      |
| `sourceAttribution` | yes      |
| `inclusionReason`   | yes      |
| `sourceYear`        | no       |

Values may remain null while quarantined. `validate_taxonomy` reports `complete` only when every required field is a non-empty string. Missing or blank required fields leave taxonomy `incomplete`. Incomplete taxonomy cannot promote: evaluation will not mark the candidate `promotable`, and `revalidate_selected_candidates` in `src/openopps/discovery/promotion.py` raises `PromotionPreviewError` (`selected candidate taxonomy is incomplete`).

These counts are frozen before-state evidence from barrier B000 (commit `8e3c797b975a1f79844c1906e96c0993d88ab1f1`), not current live catalog totals. At that baseline, 895 records have exactly 8/8 required taxonomy values, 1,975 have 0/8, no record is partially populated, and `sourceYear` is present on zero records.

| Frozen taxonomy baseline                 | Count |
| ---------------------------------------- | ----- |
| Complete required taxonomy (exactly 8/8) | 895   |
| No standard taxonomy fields (0/8)        | 1,975 |
| Partially populated records              | 0     |
| `sourceYear` present                     | 0     |

Future promotion is expected to change counts. Do not treat 895/1,975 as a current production inventory.

See [CLI](/docs/cli#quarantined-source-discovery) for scout, verify, and preview command strings.

## Support Levels [#support-levels]

| Level         | Meaning                                                                                   |
| ------------- | ----------------------------------------------------------------------------------------- |
| `detect`      | OpenOpps can preserve provider metadata and route hints but does not reliably fetch jobs. |
| `jobs`        | OpenOpps can fetch public postings for the provider.                                      |
| `unsupported` | The provider is known only as raw upstream metadata.                                      |

## Job-Capable Providers [#job-capable-providers]

<JobProviderSummary />

## Detect-Only Providers [#detect-only-providers]

`manatal` and `gem` are preserved as board metadata until stable public fetching is added. Health reports group these under `notCovered` so they are visible without being treated as failed job syncs.

## Startup-board source scope (v0.1) [#startup-board-source-scope-v01]

YC is the preferred packaged startup-board source (`yc` / `ycombinator` via the public Algolia-backed companies index). WorkAtAStartup is intentionally **not** packaged; it duplicates that discovery surface without a better public no-auth path.

Wellfound and Angel List startup discovery are **unsupported** for v0.1: public discovery depends on session or anti-bot protected pages rather than stable static no-auth assets or approved search-index endpoints. OpenOpps does not ship a Wellfound/Angel source adapter. Provider coverage JSON includes `gaps.sourceScope.unsupportedSourceDiscovery` with the release rationale.

Consider-backed sources may emit `Editorial` or misspelled `Editiorial` `job_sources` labels without a generic public ATS route. OpenOpps keeps those hints as detect-only metadata and does not register an `editorial` job provider until route-probe evidence proves a repeatable public fetch path. Audit notes live in `openspec/changes/archive/2026-07-13-provider-source-scope-hygiene/editorial-label-audit.md`.

## Public No-Auth Board Providers [#public-no-auth-board-providers]

Workable, Teamtailor, BambooHR, Rippling, and WP Job Manager are job-capable in v0.1 only through public unauthenticated board routes. Workable fetches list and per-job detail endpoints separately so `raw_listing` and `raw_detail` stay distinct for audit replay. BambooHR uses public careers endpoints such as `/careers/list` and `/careers/{job_id}/detail`; OpenOpps does not call authenticated BambooHR ATS APIs. WP Job Manager requires an explicit `/wp-json/wp/v2/job-listings` or `/jm-ajax/get_listings/` endpoint and is not inferred from arbitrary WordPress sites.

## Surplus field promotion [#surplus-field-promotion]

Greenhouse list responses with `content=true` promote `metadata`, `requisition_id`, `language`, and department/office hierarchy into `provider_extras`; prospect posts without `internal_job_id` are tagged `posting_kind=prospect`. See the S1–S4 surplus taxonomy in [Data Model](/docs/data-model#ingest-surplus-taxonomy-s1s4) and `openspec/changes/ingest-data-surplus/` for the full promotion manifest.

| Provider       | Shipped in v0.1                                                                   | Planned (manifest)                        |
| -------------- | --------------------------------------------------------------------------------- | ----------------------------------------- |
| Greenhouse     | `metadata`, `requisition_id`, `language`, department/office trees, `posting_kind` | `pay_input_ranges` (optional N+1 fetch)   |
| Workable       | `raw_listing` / `raw_detail` split                                                | —                                         |
| Lever          | —                                                                                 | `categories`, epoch dates, `sections`     |
| Ashby          | —                                                                                 | `isListed`, compensation, `workplaceType` |
| BambooHR       | —                                                                                 | `jobOpening`, requisition ids             |
| Workday        | —                                                                                 | `postedOn`, `jobDescription`              |
| Rippling       | —                                                                                 | `payRangeDetails`, `workplaceType`        |
| Teamtailor     | —                                                                                 | RSS limits; optional HTML detail fetch    |
| WP Job Manager | —                                                                                 | meta keys                                 |

## Provider Coverage [#provider-coverage]

Provider coverage reports on the persisted SQLite dataset only. It does not perform live HTTP checks, source fetches, route probes, or job syncs. The deterministic smoke data proves the report shape; published real-world percentages must be measured from representative persisted source snapshots:

```bash
uv run openopps providers coverage
uv run openopps providers coverage --source a16z --provider any --json
uv run openopps providers coverage --source a16z --provider greenhouse
uv run openopps providers audit --source a16z --json
uv run openopps admin sources yield --json
```

Use coverage to answer whether the local data is complete enough for analysis. The JSON report includes source, board, route, and job counts; route counts by provider, support level, and last status; executable route counts; missing route metadata counts; duplicate route skips derived from the durable route registry; job counts by provider, source, and board; non-supported provider coverage; detect-only provider examples; boards with job-capable hints but no executable route; and boards with executable routes but zero persisted jobs.

`admin sources yield` reports source-family conversion from persisted records only: company candidates, canonical boards, provider hints, job-capable routes, route-ready routes, active job routes, duplicate board rate, active boards added, yield score, and taxonomy totals by provider type and access type.

Coverage also reports enrichment completeness percentages from deterministic provider-field mapping. These data-quality metrics cover posting URLs, apply URLs, locations, departments, descriptions, normalized compensation or salary, remote level, and employment type.

<AuditProviderTargets />

## Route Probing [#route-probing]

Some sources report provider hints without the public route token required for job fetching. Route probing tries candidate tokens derived from upstream slugs, remote ids, names, domains, and websites. Board keys such as `a16z:acme` are durable record identifiers, not provider route-token candidates.

<div className="openopps-ref-grid">
  <div className="openopps-ref-card">
    <strong>
      Candidate tokens
    </strong>

    <span>
      Derived from source slugs, remote ids, names, domains, and websites.
    </span>
  </div>

  <div className="openopps-ref-card">
    <strong>
      Dry-run first
    </strong>

    <span>
      Route probing reports matches and unknowns without persistence by default.
    </span>
  </div>

  <div className="openopps-ref-card">
    <strong>
      Apply deliberately
    </strong>

    <span>
      Add <code>--apply</code> only after inspecting matched route metadata.
    </span>
  </div>
</div>

```bash
uv run openopps admin providers probe-routes --source a16z --provider any --limit 25 --json
```

By default, probing only checks routes missing token or URL metadata and does not persist results. Use `--include-existing` to recheck existing routes and `--apply` to persist matched metadata.

## Provider Health [#provider-health]

Provider health samples source adapters and job-capable routes, then reports status counts:

```bash
uv run openopps providers health --source a16z --provider any --limit 25 --json
```

Health checks report `active`, `empty`, `error`, `missing_route`, `not_covered`, and duplicate route skips. Add `--apply` to persist source health under `raw_metadata.health` and board-provider route health under `last_status`.

Use provider health for live sampled HTTP status. Use provider coverage for offline persisted route coverage and enrichment quality. Use provider audit for candidate-provider adoption evidence.

## Board Route Registry [#board-route-registry]

The `board_providers` registry is the executable route layer between discovered boards and job sync. Use it before large syncs to confirm which routes are ready:

```bash
uv run openopps admin providers registry --provider any
uv run openopps admin providers registry --provider any --passed-probe-only --json
uv run openopps admin providers registry --provider any --include-missing --limit 50
```

Without `--include-missing`, `admin providers registry` skips job-capable hints that still lack executable metadata, such as an Ashby board token or complete Workday CXS route. `--passed-probe-only` narrows output to routes verified by a persisted `admin providers probe-routes --apply` result.

Use [Explorer](/explorer) to inspect the generated static snapshot of persisted boards, board-provider routes, latest job rows, source/provider coverage, and data-quality signals.

## Provider Limits [#provider-limits]

* Workday support is limited to public postings visible through careers sites; it is not official tenant API access.
* Ashby sync excludes `isListed: false` direct-link-only postings from normal output.
* Overlapping board records from multiple sources are merged by company domain. Board JSON includes `source_keys` and `source_board_keys` for every source currently represented by the canonical board, and provider requests are deduped before route probes and job syncs.
* Installed Python plugins are not sandboxed and run in the same process as OpenOpps.
* Use `OPENOPPS_PLUGIN_ALLOWED` to opt in trusted entry-point names before plugin code executes.
* Use `OPENOPPS_PLUGIN_AUTOLOAD=true` only in controlled environments where every installed `openopps.plugins` entry point is trusted.
