# Remote Jobs Scraper - Employer-Direct, No Job-Board Reposts (`maydit/remote-jobs-employer-direct`) Actor

Remote postings read straight from employer boards on Greenhouse, Lever, Ashby, Recruitee and Workable, not job-board reposts. Remote is location-inferred on Greenhouse. New-since-last-run diff.

- **URL**: https://apify.com/maydit/remote-jobs-employer-direct.md
- **Developed by:** [Brandt May](https://apify.com/maydit) (community)
- **Categories:** Jobs, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.60 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## Remote Jobs Scraper - Employer-Direct (Greenhouse, Lever, Ashby, Recruitee, Workable)

Scrape **remote job postings straight from employers' own career boards** and get them back as flat CSV, JSON or Excel rows, newest first. Nothing here comes from a job board or an aggregator: every row is a posting the employer currently publishes on its own Greenhouse, Lever, Ashby, Recruitee or Workable board, read through those platforms' public job-board APIs. **No API key, no login, no account on any platform.**

Two things set this remote jobs scraper apart from a generic ATS scraper:

- **An honest remote flag.** Every row says *why* it counts as remote (`remoteBasis`). On Ashby, Lever, Recruitee and Workable the platform publishes a remote marker per posting and the Actor uses it; on Ashby, Lever and Recruitee a posting the platform marks hybrid or on-site is dropped even when its location line says "Remote" (`strictRemote`, on by default). **Greenhouse publishes no workplace field at all, so on Greenhouse remote is always inferred from the location text** ("Remote, United States", "Home based - Worldwide") - the title of this Actor says so because it matters. The same inference is the fallback on the other platforms whenever the employer left the marker blank.
- **A built-in, verified list of 70 majority-remote employer boards** (`preset: remote-first`), plus a *new since last run* mode for a daily digest and a per-run board-health report naming boards that resolved but published nothing.

Built for remote job seekers, remote-work job boards that want employer-direct listings, recruiters sourcing remote talent, and anyone tracking remote hiring at a list of companies.

### What you get

One row per remote job posting. Rows from every company are merged and sorted newest first.

| Field | What it is | Filled on the default run (800 rows, three runs on 2026-09-25) |
| --- | --- | --- |
| `ats` | Platform the board runs on: `greenhouse`, `lever`, `ashby`, `recruitee` or `workable`. | 100% |
| `company` | The slug you passed (or the preset's slug), for example `gitlab`. Stable; use it to group. | 100% |
| `companyName` | Display name as the platform publishes it (`GitLab`). Greenhouse, Recruitee and Workable send one; Lever and Ashby do not, so their rows have `null` here. | 64% |
| `title` | Job title. | 100% |
| `department` | Department the employer filed the role under. | 99.8% |
| `team` | Team (Lever, Ashby), function (Workable) or first office (Greenhouse - it has no team field). Recruitee has none. | 94.9% |
| `location` | The location line the employer published, for example `Remote, Canada; Remote, United States`. | 100% |
| `remote` | Always `true` - only remote postings are saved. Kept for schema compatibility with our ATS Job Postings Scraper. | 100% |
| `remoteBasis` | Why the row is remote: `workplaceType` (the platform marked it remote) or `location-text` (inferred from the location line - the only signal on Greenhouse). On the default run: 984 / 1,526 of the 2,510 postings kept before the total cap. | 100% |
| `workplaceType` | `remote`, `hybrid` or `onsite` as the platform publishes it. `null` when the platform has no such field (every Greenhouse row), when the employer left it blank (many Ashby rows), or on a Workable row without the telecommuting flag. | 34.8% |
| `employmentType` | Commitment as published, unnormalized: `FullTime`, `Full-time`, `Contract`, ... Greenhouse has no such field. | 33.9% |
| `postedAt` | ISO 8601 UTC timestamp of publication (Greenhouse: first published, falling back to last updated). | 100% |
| `salaryMin`, `salaryMax`, `salaryCurrency`, `salaryPeriod` | Structured pay range when the platform returns one (Lever `salaryRange`, Ashby compensation, Recruitee salary). Greenhouse and Workable never do; pay ranges those employers write into the description text are not parsed. `salaryPeriod` is `year`, `month`, `week`, `day` or `hour`. | 12% |
| `url` | Public link to the posting. | 100% |
| `jobId` | The platform's job ID, as a string. Stable - use it to dedupe across runs. | 100% |
| `description` | Only when `includeDescription` is on: the full posting as clean plain text (HTML unescaped and stripped, bullets kept as `- `, capped at 20,000 characters). | - |
| `scrapedAt` | ISO 8601 UTC timestamp of the run. | 100% |

A `SUMMARY` record is written to the run's key-value store with: companies requested / attempted / resolved and which platform each resolved on; `failedSources` (boards that errored) and `notFound` (slugs with no board anywhere); **`boardHealth`** - boards that resolved but publish 0 postings, and boards with open postings but none remote; open and remote postings seen per board; the `remoteBasis` breakdown; how many rows the `maxResults` cap cut; and whether the run stopped early on its time budget.

### The remote rule, platform by platform

| Platform | What the platform publishes | How a posting becomes remote | `remoteBasis` |
| --- | --- | --- | --- |
| Ashby | `workplaceType` Remote / Hybrid / OnSite - **often blank** (Supabase on 2026-09-25: 24 of 55 postings blank, location "Remote, Global") | Remote -> remote. Hybrid / OnSite -> dropped when `strictRemote` is on. Blank -> location text decides. | `workplaceType` or `location-text` |
| Lever | `workplaceType` remote / hybrid / onsite / unspecified | Same as Ashby; `unspecified` counts as blank. | `workplaceType` or `location-text` |
| Recruitee | `remote`, `hybrid`, `on_site` flags | Same as Ashby. | `workplaceType` or `location-text` |
| Workable | `telecommuting` true / false only - nothing separates hybrid from on-site | Flag set -> remote (`workplaceType` = `remote`). Not set -> location text decides. On the two Workable boards checked when the rule was built (zyte, remotebase - 22 remote postings), every remote posting carried the flag and none had remote in its city or country line, so the flag is the working signal there. | `workplaceType` or `location-text` |
| Greenhouse | **Nothing.** No workplace field of any kind. | Location text decides, always. | `location-text` |

"Location text decides" means the posting's location line(s) contain one of: *remote, remotely, distributed, home based, work from home, WFH, anywhere, worldwide, virtual, telecommute*. Every one of those words was seen on a live board when the list was built. Job titles are never used as location text. A country name on its own ("United States", "Spain") is **not** remote - which is why Elastic (382 open roles, all published as country names) is not in the preset even though the company is distributed. Ashby's `isRemote` flag is never used: it is `true` on hybrid postings too (checked 2026-09-25 on the OpenAI, Sentry and Alchemy boards: all 572 postings marked Hybrid had isRemote true).

### Input

| Field | Type | Default | What it does |
| --- | --- | --- | --- |
| `preset` | `remote-first` or `none` | `remote-first` | The built-in list of 70 verified boards (below). Used only when no companies are listed. |
| `companies` | string list | empty | Company slugs, auto-detected across the five platforms in the order Greenhouse, Lever, Ashby, Recruitee, Workable. Listing companies **replaces** the preset. |
| `greenhouse`, `lever`, `ashby`, `recruitee`, `workable` | string list | empty | Slugs pinned to one platform, skipping detection. |
| `titleContains` | string | - | Keep only titles containing this text (case-insensitive, partial). |
| `strictRemote` | boolean | `true` | Drop postings the platform marks Hybrid or OnSite even if the location text says remote. Off: keep them as `location-text`. On the preset this changes very little (3,292 vs 3,294 remote postings on 2026-09-25) because the boards are remote-first to begin with. |
| `onlyWithSalary` | boolean | `false` | Keep only rows with a structured `salaryMin` or `salaryMax` (never a Greenhouse or Workable row). |
| `postedWithinDays` | integer | - | Keep only postings published in the last N days. |
| `includeDescription` | boolean | `false` | Add the `description` column. |
| `maxResultsPerCompany` | integer | `100` | Cap per board, newest first. |
| `maxResults` | integer | `800` | Total cap, applied after merging every company newest first - a capped run is the freshest N postings across the list, not the first few companies. |
| `onlyNewSinceLastRun` | boolean | `false` | Only postings not seen by a previous run with this option on. See the FAQ - the first run returns everything. |
| `maxRunSeconds` | integer | `240` | Wall-clock budget. The run stops cleanly and keeps what it has. |

Every board is one request whatever the caps say, so the caps only shape the output.

### The `remote-first` preset (70 boards, verified 2026-09-25)

A board is in the list because, on the day it was checked, it resolved on exactly this platform under this slug, had **at least 5 remote postings** under the default `strictRemote` rule, and **remote postings were at least half of its open roles**. That is a measured property of the board on that day, not a statement about company policy, and boards drift: the preset is re-verified monthly with `src/preset-health.mjs` (it prints any board that no longer qualifies), and a run logs a warning naming any preset board that came back empty or non-remote.

- **Greenhouse (30, remote location-inferred):** affirm, calendly, canonical, chainguard, coinbase, consensys, cribl, customerio, dropbox, gitlab, grafanalabs, hightouch, honeycomb, lattice, launchdarkly, mercury, monzo, mozilla, outschool, pantheon, planetscale, reddit, remotecom, sourcegraph91, storyblok, tailscale, twilio, typeform, webflow, wikimedia
- **Lever (7):** anchorage, binance, metabase, neon, offchainlabs, sysdig, toptal
- **Ashby (32):** 1password, airbyte, andela, atlan, checkly, clickhouse, clickup, close, coder, confluent, docker, goteleport, helpscout, hopper, linear, mural, n8n, oyster, posthog, prefect, quora, railway, redis, render, resend, revenuecat, sanity, signoz, supabase, temporal, workos, zapier
- **Recruitee (1):** timedoctor
- **Workable (0):** none, on purpose. Zyte and Remotebase qualified on content (17 and 5 remote postings), but Workable enforces a per-IP **daily** quota - its HTTP 429 carries a `Retry-After` of about 24 hours - and on a shared platform IP that quota can be gone before a run starts. Two boards worth 22 postings are not worth a default run that is slow or warns at random. Workable boards work when you list them explicitly (`workable`); see "Data source, access and limits".

Checked the same day and deliberately left out: Elastic and Mattermost (distributed companies whose boards publish only country names), Databricks (889 open, 8% remote), Vercel (88 / 10%), Airbnb (157 / 11%), Invisible (Greenhouse `invisible` 5 open / 4 remote, `invisibletech` 18 / 2), Ghost (Greenhouse, 6 open, 0 remote), Buffer and GitBook (Ashby boards with a single posting each), Deel (Ashby board resolves but publishes 0 jobs), 15Five and Kraken (Lever, 0 jobs), and Automattic, Doist, Hotjar, Toggl and HashiCorp (Workable accounts exist but publish 0 jobs; not on the other four platforms under those slugs). If you know the right board for one of these, pass it explicitly.

On 2026-09-25 the preset had **3,662 open postings, 3,292 of them remote**; the default `maxResults` of 800 returned the newest three weeks (2026-09-04 onward) across 66 of the 70 companies - 513 Greenhouse, 203 Ashby, 82 Lever and 2 Recruitee rows.

### Example output

A real row from the default run (2026-09-25):

```json
{
  "ats": "greenhouse",
  "company": "gitlab",
  "companyName": "GitLab",
  "title": "Distinguished Engineer, Core DevOps",
  "department": "DevOps Engineering",
  "team": null,
  "location": "Remote, Canada; Remote, United Kingdom; Remote, United States",
  "remote": true,
  "remoteBasis": "location-text",
  "workplaceType": null,
  "employmentType": null,
  "postedAt": "2026-09-25T17:37:29.000Z",
  "salaryMin": null,
  "salaryMax": null,
  "salaryCurrency": null,
  "salaryPeriod": null,
  "url": "https://job-boards.greenhouse.io/gitlab/jobs/8831350002",
  "jobId": "8831350002",
  "scrapedAt": "2026-09-25T18:07:35.772Z"
}
```

### FAQ

**Is this "true remote only"?**
Not unqualified, and the Actor does not claim it. On Ashby, Lever and Recruitee the default run keeps only postings the platform itself marks Remote, or postings with no workplace type whose location line says remote. On Workable the platform's telecommuting flag is used, and it cannot tell hybrid from on-site. On Greenhouse there is no workplace field to check, so remote is inferred from the location line - an employer that writes "Remote" on a posting that is really hybrid will get through, and an employer that writes only a city on a remote role will not. `remoteBasis` tells you which case each row is, so you can filter on `workplaceType` alone if you want the strictest set (34.8% of the default run).

**What exactly does "only postings new since the last run" do?**
With the option on, the Actor records the job IDs of every remote posting on each board it read, in a named key-value store called `remote-jobs-employer-direct-state`, keyed by platform and slug. On the **first** run there is no baseline, so you get **every** remote posting and the baseline is written - the run log warns about this. From the second run on you get only IDs that were not in the baseline. A run with nothing new finishes successfully with an empty dataset and a warning; that is the expected result, not a failure. The baseline is written only on runs with the option on, it records every remote posting on the board (so a `maxResultsPerCompany` cap does not hide postings from later diffs), and it is shared by every run of this Actor on your account - two schedules watching the same board will each consume the other's new postings. Do not turn this on for a run you expect to return rows every time.

**Why is company X not found?**
The slug has to be the one in the board URL: `job-boards.greenhouse.io/gitlab` -> `gitlab`, `jobs.lever.co/toptal` -> `toptal`, `jobs.ashbyhq.com/zapier` -> `zapier`, `timedoctor.recruitee.com` -> `timedoctor`, `apply.workable.com/zyte` -> `zyte`. Some are not obvious: Sourcegraph's Greenhouse token is `sourcegraph91`, Remote.com's is `remotecom`. A slug that 404s on every platform is listed under `notFound` in `SUMMARY` and the rest of the run continues. If a company uses Workday, SmartRecruiters, iCIMS, Teamtailor or its own careers site, it cannot be read here.

**Why not SmartRecruiters?**
Because `api.smartrecruiters.com/robots.txt` disallows every crawler except LinkedInBot. This Actor stays off it.

**Does a Workable "found" mean the company hires there?**
Not always. Workable answers HTTP 200 with an empty job list for many accounts that exist but publish nothing (Automattic, HashiCorp, Doist and Hotjar all do), so during auto-detection an empty Workable hit is only used when no other platform has the company, and it is reported under `boardHealth.resolvedButEmpty`, not as a live board.

**Why is there no salary on most rows?**
Because Greenhouse and Workable return no structured pay data, and on Lever, Ashby and Recruitee it is there only when the employer fills it in. 12% of the default run's rows had one. Ranges written into the description are in `description` when you request it, unparsed - no made-up column.

**How fast is it?**
Each board is a single request; Greenhouse, Ashby and Recruitee are read four boards at a time, Lever one board per second (its robots.txt asks for a one-second crawl delay) and Workable one at a time. Measured locally on 2026-09-25, three consecutive default runs - the 70-board preset, 3,662 postings inspected, 800 rows written - took **10, 12, 21 and 26 seconds** end to end across four consecutive runs; the spread is download throughput, because the 70 boards return 51 MB of JSON (measured 2026-09-25 - Greenhouse only publishes departments together with the full posting text). Platform runs add the time to write rows to Apify storage. Long custom lists are bounded by `maxRunSeconds`, not by the number of boards: with `maxRunSeconds` 30 against 47 Lever boards, the run finished as a success at 31 seconds with 223 rows and named the 38 boards it did not reach.

### Data source, access and limits

- **Sources:** the public job-board APIs of Greenhouse (`boards-api.greenhouse.io`), Lever (`api.lever.co/v0/postings`), Ashby (`api.ashbyhq.com/posting-api/job-board`), Recruitee (`<slug>.recruitee.com/api/offers`) and Workable (`apply.workable.com/api/v1/widget/accounts`). No key, no login, no cookies. Only postings the employer has published publicly.
- **Remote is a signal, not a guarantee.** See the rule table above. `remoteBasis` is on every row so nothing is hidden.
- **Everything else is passed through as published.** Titles, departments, locations, employment types and timestamps come from the employer's own data, with its inconsistencies - `employmentType` is `FullTime` on Ashby and `Full-time` on Lever, and Greenhouse has none.
- **No personal data is extracted.** Every row describes a job posting, not a person: there is no recruiter name, email or phone field, and none is inferred. `description` reproduces the employer's own published text. Nothing is collected from candidates or applications.
- **Failures are explicit.** Boards that error are named in the log and in `SUMMARY.failedSources`; slugs found nowhere in `SUMMARY.notFound`; boards that resolve but publish nothing in `SUMMARY.boardHealth`. The run still saves everything from the boards that worked. If **every** company fails, the run fails with a message naming the likely cause instead of returning an empty dataset that looks like "no jobs".
- **Rate limits are the platforms'.** Lever asks for a one-second crawl delay and gets it. Workable enforces a per-IP **daily** quota: its HTTP 429 carries a `Retry-After` of about 24 hours (84,576 seconds measured on 2026-09-25) and its servers answer 429 and 200 alternately once the quota is used. The Actor reads Workable one board at a time, retries a 429 twice a second apart, then lists the board in `SUMMARY.failedSources` with the `Retry-After` value instead of waiting. Because Apify runs share egress IPs, an explicitly listed Workable board can fail in one run and work in the next; that is the reason no Workable board is in the preset.
- **Billing:** runs are billed per result on Apify - the current rate is on the Pricing tab of this Actor's page. There is nothing else to buy; the data sources themselves are keyless. New Apify accounts receive monthly free platform credits that cover small runs.
- **Not affiliated** with Greenhouse Software, Lever, Ashby, Recruitee, Workable or any employer in the preset. Those names are trademarks of their owners; this Actor is an independent tool that reads publicly available job-board data.

### Maintenance (for the publisher)

The preset is a snapshot. Once a month, and before any change to `src/preset.js`:

```
cd remote-jobs-scraper
node src/preset-health.mjs
```

It re-fetches every preset board with the Actor's own remote rule and exits 0 when all still qualify, 1 with an `ACTION NEEDED` list of boards to remove or re-slug, or 2 when a board could not be checked (transport or rate-limit error - re-run in a few minutes). Candidate boards can be checked before adding them: `node src/preset-health.mjs greenhouse:elastic ashby:deel`. Update `PRESET_VERIFIED_ON` in `src/preset.js` and the counts in this README after pruning.

# Actor input Schema

## `preset` (type: `string`):

'remote-first' scrapes a built-in list of 70 employer boards that were majority-remote when last verified (2026-09-25): every board had at least 5 remote postings and remote postings were at least half of its open roles. The list is in the README. The preset is used only when 'companies' and the per-platform lists are empty - listing companies replaces it. Choose 'none' when you pass your own companies and want an error rather than the preset if the list is empty.

## `companies` (type: `array`):

Company slugs to scrape, one per line. The slug is the path segment of the employer's board URL: job-boards.greenhouse.io/<slug>, jobs.lever.co/<slug>, jobs.ashbyhq.com/<slug>, <slug>.recruitee.com or apply.workable.com/<slug>. Each slug is tried on Greenhouse, Lever, Ashby, Recruitee and Workable in that order and the first board found is used. Slugs are case-insensitive. A slug with no board on any platform is reported and skipped. When you fill this in, the preset is not added.

## `greenhouse` (type: `array`):

Board tokens to read from Greenhouse only, skipping detection (e.g. 'gitlab'). Remote on Greenhouse is always inferred from the location text, because the platform publishes no workplace field.

## `lever` (type: `array`):

Company slugs to read from Lever only (e.g. 'toptal'). Lever publishes a workplaceType per posting (remote / hybrid / onsite / unspecified).

## `ashby` (type: `array`):

Board names to read from Ashby only (e.g. 'zapier'). Ashby publishes a workplaceType per posting (Remote / Hybrid / OnSite) but many employers leave it blank, in which case the location text decides.

## `recruitee` (type: `array`):

Subdomains to read from Recruitee only (e.g. 'timedoctor' for timedoctor.recruitee.com). Recruitee publishes remote / hybrid / on-site flags per posting.

## `workable` (type: `array`):

Account names to read from Workable only (e.g. 'zyte' for apply.workable.com/zyte). Workable publishes a 'telecommuting' yes/no per posting and nothing that separates hybrid from on-site; postings without the flag are checked against their location text. Workable enforces a per-IP daily quota (HTTP 429 with a Retry-After of about 24 hours), so a board here can fail in one run and work in the next; such a board is named in SUMMARY.failedSources and the rest of the run continues. That is why no Workable board is in the preset.

## `titleContains` (type: `string`):

Optional. Keep only postings whose title contains this text (case-insensitive, partial match), for example 'engineer' or 'account executive'. Applied after the remote rule.

## `strictRemote` (type: `boolean`):

On (default): when the platform publishes a workplace type and it is Hybrid or OnSite, the posting is dropped even if its location text says 'Remote'. Off: such a posting is kept and marked remoteBasis 'location-text'. Postings with no workplace type at all (every Greenhouse posting, and many Ashby ones) are decided by their location text either way.

## `onlyWithSalary` (type: `boolean`):

Keep only postings where the platform returned a structured salary (salaryMin or salaryMax). Lever, Ashby and Recruitee publish one when the employer fills it in; Greenhouse and Workable never do, so this drops every Greenhouse and Workable row. On the default preset run of 2026-09-25, 12% of the 800 rows carried a salary.

## `postedWithinDays` (type: `integer`):

Optional. Keep only postings whose postedAt is within the last N days. Rows are always sorted newest first, so this simply trims the tail. Leave empty for no date filter.

## `includeDescription` (type: `boolean`):

Add a 'description' column with the posting text as clean plain text (HTML unescaped and stripped, list items kept as '- ' bullets, capped at 20,000 characters). Off by default because it multiplies the dataset size.

## `maxResultsPerCompany` (type: `integer`):

Upper bound on remote postings kept from any one board, newest first. Every board is fetched in one request regardless, so this only shapes the output.

## `maxResults` (type: `integer`):

Upper bound on rows in the dataset. Postings from every company are merged and sorted newest first before this cap applies, so a capped run is the freshest N remote postings across the whole list, not the first few companies. The default preset had 3,292 remote postings on 2026-09-25, so the default of 800 returns the newest 800 of them; raise it for the full list.

## `onlyNewSinceLastRun` (type: `boolean`):

Save only remote postings this Actor has not seen before. The job IDs of every remote posting on each board are recorded in a named key-value store ('remote-jobs-employer-direct-state') and compared on the next run with this option on. The FIRST run has no baseline, so it returns every posting and records the baseline (the log says so); from the second run on you get only new ones. A run with nothing new finishes successfully with an empty dataset. Do not enable this on a run you expect to return rows every time.

## `maxRunSeconds` (type: `integer`):

Wall-clock budget for fetching boards. When it is reached the Actor stops cleanly, keeps everything already collected, and names the companies it did not reach - instead of being killed with a failed run. The default preset finishes in well under a minute; raise this only for very long custom lists.

## Actor input object example

```json
{
  "preset": "remote-first",
  "strictRemote": true,
  "onlyWithSalary": false,
  "includeDescription": false,
  "maxResultsPerCompany": 100,
  "maxResults": 800,
  "onlyNewSinceLastRun": false,
  "maxRunSeconds": 240
}
```

# Actor output Schema

## `results` (type: `string`):

One row per remote job posting: ats, company, title, department, team, location, remote, remoteBasis (workplaceType | location-text), workplaceType, employmentType, postedAt, salaryMin/Max/Currency/Period, url, jobId, optional description, scrapedAt.

## `summary` (type: `string`):

Totals for the run: companies requested / resolved / not found / failed, open and remote postings seen per board, boards that resolved but publish no jobs or no remote jobs, remoteBasis breakdown, and whether the run stopped early on its time budget.

# 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 = {
    "preset": "remote-first"
};

// Run the Actor and wait for it to finish
const run = await client.actor("maydit/remote-jobs-employer-direct").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 = { "preset": "remote-first" }

# Run the Actor and wait for it to finish
run = client.actor("maydit/remote-jobs-employer-direct").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 '{
  "preset": "remote-first"
}' |
apify call maydit/remote-jobs-employer-direct --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,maydit/remote-jobs-employer-direct"
        }
    }
}
```

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/quReySVj4RslKc6Ri/builds/goZRSVkq2MgOqox5c/openapi.json
