logoPandorLabs
Lead Generation

Company Job Openings

Open roles published directly by employers on their own careers pages, in one consistent schema.

company-job-openingslead-generationjobshiringemployers

1,678,727 rows in the dataset

Download the dataset

Get every row this scraper has collected as one CSV or JSONL file. If the data is older than maxAgeHours, the scraper is re-run with its configured settings first. The key needs the datasets:export scope.

DOWNLOAD_ID=$(curl -s -X POST "https://api.pandorlabs.com/v1/datasets/company-job-openings/downloads" \  -H "Authorization: Bearer $PANDORLABS_KEY" \  -H "Content-Type: application/json" \  -d '{"maxAgeHours":24,"format":"csv"}' | jq -r '.id')while true; do  STATUS=$(curl -s "https://api.pandorlabs.com/v1/downloads/$DOWNLOAD_ID" \    -H "Authorization: Bearer $PANDORLABS_KEY" | jq -r '.status')  [ "$STATUS" != "refreshing" ] && break  sleep 5donecurl -s "https://api.pandorlabs.com/v1/downloads/$DOWNLOAD_ID/file" \  -H "Authorization: Bearer $PANDORLABS_KEY" \  -o dataset.csv

At its current 1,678,727 rows, this download costs 16,788 credits when the data is fresh enough, or 33,576 credits when it has to be refreshed first.

The rate is 1 credit per 100 rows cached and 2 credits live, charged on the rows in the dataset when the download becomes ready. A failed refresh is not charged. The file stays downloadable for 24 hours. See pricing.

Start a run

To choose the targets yourself, send the parameters below as input. The call returns 202 with the run; the key needs the scrapers:run scope and the account a positive credit balance.

curl -X POST "https://api.pandorlabs.com/v1/scrapers/company-job-openings/runs" \  -H "Authorization: Bearer $PANDORLABS_KEY" \  -H "Content-Type: application/json" \  -d '{    "input": {      "boardSource": "targets",      "targets": [        "greenhouse:stripe",        "lever:spotify",        "ashby:ramp",        "smartrecruiters:smartrecruiters",        "personio:personio"      ],      "keywords": [        "engineer",        "data",        "scraping"      ],      "remoteOnly": false    }  }'

A run is billed at the live rate once it finishes: 2 credits per 100 rows it collected. A run that collects nothing costs nothing.

Parameters

Every parameter is optional. Anything you leave out uses the default shown here; an unknown name or an invalid value is rejected with a 400 that lists each problem.

Targeting

  • boardSource

    enum

    Boards to collect

    The list below is one run against named employers. AI data & labeling companies is a maintained list of the data-annotation and model-training vendors (Surge AI, Appen, Cohere, xAI, Encord, …). The registry is every board discovery has ever found — tens of thousands, and where the volume is. Discovery sweeps the crawl index for new ones first, which adds an hour or so to the front of the run.

    Default
    targets
    Allowed values
    targets
    ai-data
    registry
    discover
  • targets

    string[]

    Boards

    One entry per employer, as `board:token` or a pasted careers URL. Boards: greenhouse, lever, ashby, smartrecruiters, personio, workday, workable, breezy, recruitee, bamboohr, teamtailor (a Teamtailor careers site on its own domain is named by host, e.g. `teamtailor:careers.example.com`). Ignored unless the source above is set to this list.

    Default
    ["greenhouse:stripe","lever:spotify","ashby:ramp","smartrecruiters:smartrecruiters","personio:personio"]
    Example
    greenhouse:stripe, https://jobs.lever.co/spotify
  • keywords

    string[]

    Title keywords

    Keeps only postings whose title contains one of these. Empty keeps everything the board publishes.

    Default
    none
    Example
    engineer, data, scraping
  • remoteOnly

    boolean

    Remote roles only

    Only keeps postings the board explicitly marks remote. Boards that publish no remote flag contribute nothing under this.

    Default
    false
  • postedWithinDays

    number

    Posted within (days)

    Drop postings older than this. 0 keeps every open role. A low value turns the run into a "who started hiring this week" feed.

    Default
    0
    Range
    0 to 3650

Collection

  • maxJobsPerBoard

    number

    Max jobs per board

    A safety stop, not a target. Set above the largest board you expect: a board that hits it is truncated, and the run says so by name.

    Default
    25000
    Range
    1 to 100000
  • maxBoards

    number

    Max boards per run

    0 collects every board the source offers. Set it to cap a first registry run, which otherwise reads tens of thousands of boards.

    Default
    0
    Range
    0 to 1000000
  • includeDescription

    boolean

    Store job descriptions

    Off by default: one large employer's descriptions run to megabytes of repeated boilerplate. On, each is capped.

    Default
    false
  • maxDescriptionChars

    number

    Description cap (characters)

    Default
    5000
    Range
    200 to 100000
  • concurrency

    number

    Concurrent boards

    Boards are independent, and these are the employers’ own endpoints rather than an aggregator defending itself, so this can be raised further than a search scraper’s. A registry sweep of tens of thousands of boards wants the top of this range.

    Default
    8
    Range
    1 to 50
  • requestDelayMs

    number

    Delay between requests (ms)

    Only applies between pages of a paged board; a one-shot board makes a single request.

    Default
    500
    Range
    0 to 30000

Discovery

  • discoverSources

    enum[]

    Indexes to sweep

    Common Crawl is a periodic snapshot and answers in minutes. Wayback is twenty years of the same hosts and holds an order of magnitude more — 367 pages of Lever boards against Common Crawl’s one — but takes about an hour. Lever is only reachable through Wayback and older crawl editions, since it disallows crawling now.

    Default
    ["commoncrawl"]
    Allowed values
    commoncrawl
    wayback
  • discoverCrawls

    number

    Crawl editions to sweep

    Each edition is a partial sample of the web, so boards keep accumulating as more are unioned. Reaching back several years is also the only way Lever boards are found at all — it disallows crawling now, so only older editions hold them.

    Default
    4
    Range
    1 to 30
  • retryFailed

    boolean

    Only retry boards that errored

    Reads only the registry boards whose last read ended in an error rather than in an answer — the cheap pass after a big one, since a rate-limited board is unread rather than empty.

    Default
    false
  • skipDeadAfter

    number

    Skip boards empty this many runs

    Registry boards that keep coming back empty are employers who closed the board or changed ATS. Skipping them is what keeps a full sweep from spending most of its requests on the dead. 0 collects everything on file.

    Default
    3
    Range
    0 to 100
Input as JSON Schema

The same schema the API validates against. Use it as a tool input schema or to generate a typed client.

inputSchema
{  "type": "object",  "additionalProperties": false,  "properties": {    "boardSource": {      "type": "string",      "enum": [        "targets",        "ai-data",        "registry",        "discover"      ],      "title": "Boards to collect",      "description": "The list below is one run against named employers. AI data & labeling companies is a maintained list of the data-annotation and model-training vendors (Surge AI, Appen, Cohere, xAI, Encord, …). The registry is every board discovery has ever found — tens of thousands, and where the volume is. Discovery sweeps the crawl index for new ones first, which adds an hour or so to the front of the run.",      "default": "targets"    },    "targets": {      "type": "array",      "items": {        "type": "string"      },      "maxItems": 1000,      "title": "Boards",      "description": "One entry per employer, as `board:token` or a pasted careers URL. Boards: greenhouse, lever, ashby, smartrecruiters, personio, workday, workable, breezy, recruitee, bamboohr, teamtailor (a Teamtailor careers site on its own domain is named by host, e.g. `teamtailor:careers.example.com`). Ignored unless the source above is set to this list.",      "default": [        "greenhouse:stripe",        "lever:spotify",        "ashby:ramp",        "smartrecruiters:smartrecruiters",        "personio:personio"      ],      "examples": [        "greenhouse:stripe, https://jobs.lever.co/spotify"      ]    },    "keywords": {      "type": "array",      "items": {        "type": "string"      },      "maxItems": 1000,      "title": "Title keywords",      "description": "Keeps only postings whose title contains one of these. Empty keeps everything the board publishes.",      "default": [],      "examples": [        "engineer, data, scraping"      ]    },    "remoteOnly": {      "type": "boolean",      "title": "Remote roles only",      "description": "Only keeps postings the board explicitly marks remote. Boards that publish no remote flag contribute nothing under this.",      "default": false    },    "postedWithinDays": {      "type": "number",      "minimum": 0,      "maximum": 3650,      "title": "Posted within (days)",      "description": "Drop postings older than this. 0 keeps every open role. A low value turns the run into a \"who started hiring this week\" feed.",      "default": 0    },    "maxJobsPerBoard": {      "type": "number",      "minimum": 1,      "maximum": 100000,      "title": "Max jobs per board",      "description": "A safety stop, not a target. Set above the largest board you expect: a board that hits it is truncated, and the run says so by name.",      "default": 25000    },    "maxBoards": {      "type": "number",      "minimum": 0,      "maximum": 1000000,      "title": "Max boards per run",      "description": "0 collects every board the source offers. Set it to cap a first registry run, which otherwise reads tens of thousands of boards.",      "default": 0    },    "discoverSources": {      "type": "array",      "items": {        "type": "string",        "enum": [          "commoncrawl",          "wayback"        ]      },      "uniqueItems": true,      "title": "Indexes to sweep",      "description": "Common Crawl is a periodic snapshot and answers in minutes. Wayback is twenty years of the same hosts and holds an order of magnitude more — 367 pages of Lever boards against Common Crawl’s one — but takes about an hour. Lever is only reachable through Wayback and older crawl editions, since it disallows crawling now.",      "default": [        "commoncrawl"      ]    },    "discoverCrawls": {      "type": "number",      "minimum": 1,      "maximum": 30,      "title": "Crawl editions to sweep",      "description": "Each edition is a partial sample of the web, so boards keep accumulating as more are unioned. Reaching back several years is also the only way Lever boards are found at all — it disallows crawling now, so only older editions hold them.",      "default": 4    },    "retryFailed": {      "type": "boolean",      "title": "Only retry boards that errored",      "description": "Reads only the registry boards whose last read ended in an error rather than in an answer — the cheap pass after a big one, since a rate-limited board is unread rather than empty.",      "default": false    },    "skipDeadAfter": {      "type": "number",      "minimum": 0,      "maximum": 100,      "title": "Skip boards empty this many runs",      "description": "Registry boards that keep coming back empty are employers who closed the board or changed ATS. Skipping them is what keeps a full sweep from spending most of its requests on the dead. 0 collects everything on file.",      "default": 3    },    "includeDescription": {      "type": "boolean",      "title": "Store job descriptions",      "description": "Off by default: one large employer's descriptions run to megabytes of repeated boilerplate. On, each is capped.",      "default": false    },    "maxDescriptionChars": {      "type": "number",      "minimum": 200,      "maximum": 100000,      "title": "Description cap (characters)",      "default": 5000    },    "concurrency": {      "type": "number",      "minimum": 1,      "maximum": 50,      "title": "Concurrent boards",      "description": "Boards are independent, and these are the employers’ own endpoints rather than an aggregator defending itself, so this can be raised further than a search scraper’s. A registry sweep of tens of thousands of boards wants the top of this range.",      "default": 8    },    "requestDelayMs": {      "type": "number",      "minimum": 0,      "maximum": 30000,      "title": "Delay between requests (ms)",      "description": "Only applies between pages of a paged board; a one-shot board makes a single request.",      "default": 500    }  }}

Run, poll, read rows

The run is asynchronous. Poll it until it reaches a terminal status and its billing is paid, then page through its rows. Until then the rows endpoint answers 409 (still queued or running) or 402 (payment required).

RUN_ID=$(curl -s -X POST "https://api.pandorlabs.com/v1/scrapers/company-job-openings/runs" \  -H "Authorization: Bearer $PANDORLABS_KEY" \  -H "Content-Type: application/json" \  -d '{    "input": {      "boardSource": "targets",      "targets": [        "greenhouse:stripe",        "lever:spotify",        "ashby:ramp",        "smartrecruiters:smartrecruiters",        "personio:personio"      ],      "keywords": [        "engineer",        "data",        "scraping"      ],      "remoteOnly": false    }  }' | jq -r '.id')while true; do  RUN=$(curl -s "https://api.pandorlabs.com/v1/runs/$RUN_ID" \    -H "Authorization: Bearer $PANDORLABS_KEY")  STATUS=$(echo "$RUN" | jq -r '.status')  BILLING=$(echo "$RUN" | jq -r '.billing.status')  case "$STATUS" in queued|running) ;; *) [ "$BILLING" != "pending" ] && break ;; esac  sleep 5doneecho "$RUN" | jq '{status, rowCount, billing}'curl -s "https://api.pandorlabs.com/v1/runs/$RUN_ID/rows?page=1&limit=100" \  -H "Authorization: Bearer $PANDORLABS_KEY"

See the API reference for pagination, cancelling and listing runs.

Output fields

Rows have no fixed schema; these are the fields observed in the rows collected so far. Coverage is the share of rows where the field is present.

FieldTypeCoverage
additional_locationsarray100%
apply_urlstring70%
boardstring100%
companystring99%
company_tokenstring100%
compensation_currencystring2%
compensation_maxnumber2%
compensation_minnumber2%
compensation_textstring4%
countrystring26%
departmentstring43%
employment_typestring48%
job_idstring100%
locationstring98%
posting_keystring100%
remoteboolean96%
senioritystring18%
source_urlstring92%
teamstring10%
titlestring100%
workplace_typestring10%
board_updated_atdate9%
created_atdate100%
first_seen_atdate100%
last_seen_atdate100%
posted_atdate45%
updated_atdate100%
_idstring100%

© 2026 PandorLabs LLC. All rights reserved.