# Greenhouse Jobs API (`jobscrawler/greenhouse-jobs-api`) Actor

Extract live Greenhouse jobs and full descriptions directly from public job-board APIs. Filter by keyword, location, and company. No API key required in direct mode; optional Fantastic.jobs mode supports your own key.

- **URL**: https://apify.com/jobscrawler/greenhouse-jobs-api.md
- **Developed by:** [Jobs Scraper](https://apify.com/jobscrawler) (community)
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 84.2% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.99 / 1,000 results

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

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

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Greenhouse Jobs API

### Overview

Rich Greenhouse ATS records from Fantastic.jobs with company and AI enrichment. This folder is a complete, standalone Apify Actor. Its layout intentionally follows the workspace’s `jobright-scraper`: source code lives under `src/`, platform definitions live under `.actor/`, the container is defined by `Dockerfile`, and the dataset can be checked with `dataset-validator.js`. It does not import code from another scraper folder, so it can be pushed, built, scheduled, or versioned independently.

The live extraction path is the official Fantastic.jobs /v1/active-ats endpoint with the fixed source filter `greenhouse`. Live runs require `fantasticJobsApiKey` (or `FANTASTIC_JOBS_API_KEY`). Credentials are sent only in the relevant request header. The Actor never prints them, stores them in the dataset, or writes them to diagnostics.

### Extraction design

The Actor prefers structured public data. It normalizes source records into a stable job/profile contract and also retains every upstream field inside `rawData`. Null and undefined source values are removed recursively. Normalized fields use empty strings, empty arrays, or empty objects when an optional value is not published, so dataset records contain no JSON `null` values. This is deliberate: an empty value means “not supplied by the source,” not an invented fact.

For job records, the output can include identifiers, title, company, all locations, salary range and unit, employment type, workplace mode, dates, description text and HTML, responsibilities, requirements, benefits, skills, taxonomy, education, language, visa information, application links, recruiter data, organization enrichment, funding, classifications, and structured geocoding. Source-specific fields that do not have a normalized column remain available in `rawData`.

### Inputs

The full machine-readable input contract is in `.actor/input_schema.json`. Every Actor defaults to a maximum of ten results and enforces a minimum request of ten. Depending on this source, useful fields include search terms, location, time window, start URLs, concurrency, richer-description toggles, and secret credentials. Advanced filters are passed only as documented primitive query parameters; the Actor does not execute arbitrary code from input.

### Local development and verification

Install dependencies and run a ten-record smoke test:

```bash
npm install
apify run --purge --input '{"sampleMode":true,"maxItems":10}'
npm run validate:data
```

A source-live example is:

```bash
apify run --purge --input '{"fantasticJobsApiKey":"<secret>","keyword":"software engineer","maxItems":10}'
npm run validate:data
```

The optional `sampleMode` reads Fantastic.jobs’ official published sample. Source-specific filters are not applied to that file, so this mode verifies the contract and rich-field normalizer, not the live filtered endpoint.

After each run, inspect `storage/datasets/default` for records and `storage/key_value_stores/default/RUN_SUMMARY.json` for machine-readable status. `SOURCE_METADATA` records the provider, source counts, and mode. A failure creates a sanitized `ERROR_DIAGNOSTIC`; it never creates fake fallback jobs or silently converts an access error into `no_results`.

### Production behavior

Push this folder as its own Actor with `apify push`. Configure secrets in Apify’s encrypted input or environment settings, not in source files. The output schema exposes direct links to the dataset, run summary, and diagnostic store. Deduplication uses a stable source ID or canonical URL. HTTP requests use bounded timeouts and limited retry with backoff for temporary network or rate-limit failures.

The Actor reads public job or profile information only. Availability and field completeness depend on what the upstream source publishes at run time. Empty optional fields are preserved honestly; values are never guessed. Respect the source’s terms, applicable privacy rules, API plan limits, and retention requirements when scheduling or redistributing output.

### Source identity

- Standalone actor: `greenhouse-jobs-api`
- Original catalog reference ID: `FymeLAffo7UYLWagM`
- Provider mode: `fantastic`
- Source/platform: `Fantastic.jobs Greenhouse`
- Record type: `job`
- Default output request: `10`

## Live source update (2026-10-21)

The default `dataSource: "direct"` reads current public greenhouse board APIs from `startUrls`. No vendor API key is needed. Default URLs are prefilled in the input form. Full source descriptions and provenance are returned; no sample or synthetic fallback is allowed. Empty valid searches return zero records; unavailable sources fail instead of pretending success.

Use `dataSource: "fantastic"` with your `fantasticJobsApiKey` for the original paid feed, vendor time windows, and enrichment. Direct mode does not provide Fantastic.jobs enrichment or historical time-window filtering. The older vendor-specific reference below applies only to Fantastic mode.

# Changelog

This Actor's version history is a separate document: https://apify.com/jobscrawler/greenhouse-jobs-api/changelog.md

# Actor input Schema

## `dataSource` (type: `string`):

Direct reads current public board APIs without a vendor API key. Fantastic requires your paid API key and enables vendor filters/enrichment.

## `startUrls` (type: `array`):

Greenhouse, Ashby, or Lever board URLs, restricted to the Actor platform. Custom hosts are rejected.

## `fantasticJobsApiKey` (type: `string`):

Secret bearer key for live API requests. It is never written to logs or datasets.

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

Optional title filter. Leave empty to receive the newest available records.

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

Optional full-name location filter, such as Germany or Berlin, Germany.

## `organization` (type: `string`):

Case-insensitive company name filter for direct mode; exact vendor filter in Fantastic mode.

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

Maximum live records to return.

## `timeRange` (type: `string`):

Fantastic mode only: active-feed time window. Direct mode returns currently published jobs, not a historical archive.

## `descriptionFormat` (type: `string`):

HTML retains the richest original description payload.

## `includeCompanyDetails` (type: `boolean`):

Fantastic mode only: request vendor company enrichment. Direct mode returns fields available publicly.

## `includeBetaFields` (type: `boolean`):

Fantastic mode only: request vendor beta classifications. Direct mode does not invent enrichment.

## `advancedFilters` (type: `object`):

Additional documented Fantastic.jobs query parameters.

## `requestTimeoutSecs` (type: `integer`):

Per-request timeout in seconds.

## Actor input object example

```json
{
  "dataSource": "direct",
  "startUrls": [
    {
      "url": "https://job-boards.greenhouse.io/duolingo"
    }
  ],
  "keyword": "",
  "location": "",
  "organization": "",
  "maxItems": 10,
  "timeRange": "7d",
  "descriptionFormat": "html",
  "includeCompanyDetails": true,
  "includeBetaFields": true,
  "advancedFilters": {},
  "requestTimeoutSecs": 45
}
```

# Actor output Schema

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

Dataset containing normalized non-null records plus complete cleaned rawData.

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

Machine-readable status, data provenance mode, counts, timing, and null validation.

## `files` (type: `string`):

Key-value store containing source metadata and any sanitized error diagnostic.

# 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 = {
    "startUrls": [
        {
            "url": "https://job-boards.greenhouse.io/duolingo"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("jobscrawler/greenhouse-jobs-api").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 = { "startUrls": [{ "url": "https://job-boards.greenhouse.io/duolingo" }] }

# Run the Actor and wait for it to finish
run = client.actor("jobscrawler/greenhouse-jobs-api").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 '{
  "startUrls": [
    {
      "url": "https://job-boards.greenhouse.io/duolingo"
    }
  ]
}' |
apify call jobscrawler/greenhouse-jobs-api --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,jobscrawler/greenhouse-jobs-api"
        }
    }
}
```

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/BsKg40UwQiAL7tdOn/builds/Eg0qp43NHfS0jpSYL/openapi.json
