# Remote Job Feed with Geo-Eligibility (`refined_liberty/remote-job-feed-geo-eligibility`) Actor

Jobs from Greenhouse, Lever and Ashby boards with detected eligible countries, remote scope and contract type. Filter for jobs you can actually do from your country; pay only for matching jobs.

- **URL**: https://apify.com/refined_liberty/remote-job-feed-geo-eligibility.md
- **Developed by:** [Marc G](https://apify.com/refined_liberty) (community)
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$2.00 / 1,000 job in datasets

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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 Job Feed with Geo-Eligibility

**Remote jobs you can actually do from your country.**

This Actor collects job postings from public **Greenhouse**, **Lever** and **Ashby** job boards and works out, for every job, **which countries you may work from**. "Remote" on a job board often means "remote, but US only" or "remote within EMEA". This Actor turns that into structured data you can filter on.

- 🌍 `eligibleCountries`, `remoteScope` (worldwide / regional / country) and `isWorldwide` on every job
- 🔎 Evidence for every decision: the exact location text or sentence it is based on
- 🎯 Filters: country, remote/hybrid/on-site, keywords, departments, posting date, employment and contract type
- 💸 **You only pay for jobs that pass your filters.** Jobs that are filtered out are free.
- 🧾 Built-in, verified list of 240+ companies with public boards, so you can start without collecting slugs

### Who is this for?

- **Job board operators** who want a feed of remote jobs per country or region
- **Recruiters and sourcers** tracking hiring at specific companies
- **Job seekers** who are tired of clicking "Remote" jobs that turn out to be US-only

### How to use it

1. Add companies: board slugs (`gitlab`), prefixed slugs (`ashby:linear`, `lever:palantir`, `greenhouse:stripe`) or board URLs (`https://jobs.ashbyhq.com/linear`). Or tick **Add the built-in list of remote-friendly companies**.
2. Choose the countries you want to work from, for example `Germany`.
3. Optionally narrow down: workplace type, keywords, departments, "posted within the last N days".
4. Run. Results appear in the dataset; a per-company report is saved as `RUN_REPORT` in the key-value store.

#### Example input

```json
{
    "companies": ["gitlab", "ashby:linear", "https://job-boards.greenhouse.io/grafanalabs"],
    "useStarterList": false,
    "countries": ["DE"],
    "workplaceTypes": ["remote"],
    "keywords": ["engineer", "developer"],
    "postedWithinDays": 30
}
```

#### Example output (shortened)

```json
{
    "id": "greenhouse:algolia:5854606004",
    "companyName": "Algolia",
    "title": "Commercial Account Executive, DACH",
    "url": "https://job-boards.greenhouse.io/algolia/jobs/5854606004",
    "locations": ["Remote - Germany"],
    "workplaceType": "remote",
    "remoteScope": "single-country",
    "remoteScopeLabel": "Germany only",
    "isWorldwide": false,
    "eligibleCountries": ["DE"],
    "eligibleRegions": [],
    "eligibilityConfidence": "high",
    "eligibilityEvidence": [{ "source": "location", "text": "Remote - Germany" }],
    "timezoneRequirement": null,
    "employmentType": "unknown",
    "engagementType": "unknown",
    "postedAt": "2026-04-07T15:53:42.000Z",
    "matchedCountries": ["DE"],
    "descriptionText": "Algolia is ..."
}
```

### Output fields

| Field | Description |
|---|---|
| `id` | Stable unique id: `source:board:jobId` |
| `source`, `companySlug`, `companyName`, `jobId` | Where the job comes from (Lever and Ashby publish no company name: it comes from the built-in list, otherwise from the board slug, e.g. `ava-labs` becomes `Ava Labs`) |
| `title`, `url`, `applyUrl`, `department`, `team`, `locations` | As published by the company |
| `workplaceType` | `remote`, `hybrid`, `onsite` or `unknown`. A job counts as **remote if it offers a remote option** |
| `remoteScope` | `worldwide`, `regional`, `multi-country`, `single-country`, `not-remote` or `unknown` |
| `remoteScopeLabel` | Human-readable summary, e.g. `Worldwide`, `EMEA`, `Germany only`, `Hybrid: United Kingdom` |
| `isWorldwide` | `true` if the job can be done from (almost) anywhere. `eligibleCountries` is then empty |
| `eligibleCountries` | ISO 3166-1 alpha-2 codes of the countries you may work from, with regions expanded |
| `eligibleRegions` | Regions named in the posting, e.g. `EMEA`, `EU`, `LATAM` |
| `excludedCountries` | Countries the posting explicitly excludes |
| `eligibilityConfidence` | `high`, `medium`, `low`, or `null` if the posting does not say (see below) |
| `eligibilityEvidence` | The texts the decision is based on: `location`, `structured` (ATS field), `title` or `description` |
| `timezoneRequirement` | Sentence stating a time-zone requirement, if any |
| `employmentType` | `full-time`, `part-time`, `internship`, `temporary` or `unknown` |
| `engagementType` | `employee`, `contractor`, `employer-of-record` or `unknown` |
| `postedAt`, `postedAtSource` | Publish date (Greenhouse: first publish date; Lever: created; Ashby: published) |
| `salary` | Structured salary range if the ATS publishes one (Lever, Ashby) |
| `matchedCountries` | Which of your requested countries this job is open to |
| `descriptionText` / `descriptionHtml` | Description as plain text and/or sanitised HTML |

**For `onsite` and `hybrid` jobs**, `eligibleCountries` are the countries of the offices: you need to live there.

### How eligibility is detected

The Actor reads three sources, in order of reliability:

1. **The location field(s)** of the posting, for example `Remote - USA`, `Home based - EMEA`, `Remote, Canada; Remote, United States`, `SF, NYC, Remote`. It recognises all countries, common regions (EU, EEA, Europe, EMEA, DACH, Nordics, CEE, LATAM, North America, Americas, APAC, ...), US states, Canadian provinces and about 400 tech-hub cities, with handling for ambiguous names ("London, Ontario", "Atlanta, Georgia" vs. "Tbilisi, Georgia", "Berlin, DE" vs. Delaware).
2. **Structured ATS fields**, such as Ashby's address country, Lever's country or a Greenhouse "remote countries" custom field.
3. **Explicit sentences in the description**, for example *"This role is open to candidates based in the UK or Ireland"* or *"Candidates outside the US will not be considered"*.
4. **The job title**, only for a bare `Remote` location, and only if a segment names nothing but places: `Salesforce Lead - USA`, `Engineer (United Kingdom)`. This gives `medium` confidence.

A job counts as remote only if the posting says so for this job. Company-wide statements such as "we are a distributed company" do not count, because they also appear on office jobs. Talent pools and general applications get `low` confidence, so they do not pass a default country filter.

Rules that keep results trustworthy:

- **The description cannot widen a stated location.** A job listed as `Remote - USA` stays US-only even if the company's boilerplate says "we work from anywhere in the world". If the location is just `Remote`, an explicit sentence about this role (*"You must be based in Germany"*, *"You can work from anywhere in the world"*) fills it in with `medium` confidence. Sentences about the company (*"our team works remotely from Brazil to Japan"*) are ignored.
- **"Anywhere in X" means X.** `Remote - Anywhere in the US` is US-only, not worldwide.
- **Inferences are marked, not hidden.** If nothing states where the job may be done from, `eligibilityConfidence` is `null`. Weaker inferences, such as a remote job whose only hint is the company's office list, get `low`. Both are excluded from country-filtered results (and not charged) unless you lower `minConfidence` or enable *Include jobs with unknown location rules*.
- **Contradictions are flagged**, not hidden: if the location says `Singapore` but the text says "This role is based in San Francisco", confidence is `low`.

#### Confidence levels

| Level | Meaning | Examples |
|---|---|---|
| `high` | Stated explicitly | `Remote - Germany`, `Remote - EU`, `Remote - Anywhere in the US`, an explicit remote-country list, a location confirmed by a "must be located in France" sentence |
| `medium` | Stated, but broad or from a single source | Broad regions such as EMEA, APAC or LATAM, a structured ATS country for a remote job, a remote option listed next to cities, a bare `Remote` plus one sentence in the description |
| `low` | Inferred or contradictory | A remote job whose only hint is the company's office list, `Global (US-based preferred)`, location and description naming different countries |

**Broad regions are taken literally.** `EMEA` covers Europe, the Middle East and Africa (116 countries), but many employers mean mainly the UK and EU. If you search from a country at the edge of such a region, check `eligibleRegions` and `eligibilityEvidence`, or set `minConfidence` to `high` to get only explicitly stated countries and precise regions (EU, EEA, DACH, Nordics, ...).

By default, country filters accept `medium` and better (`minConfidence`).

#### Regions and sanctions

Regions are business regions, not strict geography, and deliberately conservative: *North America* means US and Canada; *Europe* and *EMEA* do not include Russia or Belarus. Comprehensively embargoed countries (Cuba, Iran, North Korea, Syria) never match, not even for worldwide jobs.

Eligibility here means **"you may be based there for this job"**. It does not cover visas or work permits.

### Pricing

Pay per result: you pay **only for jobs written to the dataset**, which means only for jobs that pass all your filters. Fetching and filtering out non-matching jobs costs nothing. Use **Maximum number of jobs** or the run's spending limit to cap costs. The run stops cleanly when the limit is reached. If the platform restarts a run, jobs already stored are not stored or charged again.

### Performance

All three sources are public JSON APIs, with no browser and no proxies. The built-in list of 240+ companies (about 20,000 jobs) took about 47 seconds with 4 GB of memory and about 3 minutes with 1 GB, because the platform assigns CPU in proportion to memory. A list of a few dozen companies is correspondingly faster.

### Limitations

- Only **public** boards on Greenhouse, Lever and Ashby. Workday, SmartRecruiters, Personio and other ATS are not covered yet.
- Greenhouse publishes salary ranges only per job, so `salary` is empty for Greenhouse in this version.
- Greenhouse has no standard field for the employment type. It is read from the title and from explicit sentences about the role, and about two thirds of Greenhouse jobs state nothing usable: their `employmentType` and `engagementType` are `unknown`. Filtering on these two fields therefore drops those jobs.
- Greenhouse boards hosted on the EU instance are not supported yet.
- Detection is rule-based. Job postings are written by humans, so use `eligibilityEvidence` to double-check edge cases.
- `excludeKeywords` checks only the job title. `keywords`, `departments` and `excludeKeywords` match whole words: `IT` does not match `Recruiting`, and `engineer` does not match `Engineering`, so use department names as published.
- States or cities a posting excludes (`Remote - US (excluding California)`, *"we cannot hire in Alaska or Hawaii"*) are not modelled: the country stays eligible with `medium` confidence and the exclusion is listed in `eligibilityEvidence`.

### Data and privacy

The Actor reads only public job postings published by companies for applicants. It does not log in, does not bypass any protection and does not collect personal data. Requests go only to the official public APIs (`boards-api.greenhouse.io`, `api.lever.co`, `api.eu.lever.co`, `api.ashbyhq.com`); company URLs you enter are never fetched directly. Job descriptions in `descriptionHtml` are sanitised (no scripts, styles, iframes, images, forms or event handlers; links are restricted to http/https/mailto), so they are safe to render on your site. All other fields (`title`, `locations`, `descriptionText`, ...) are plain text as published by the company and may contain characters such as `<`: escape them when you insert them into HTML.

### FAQ

**My company is not found.** Check the slug in the board URL, e.g. `jobs.ashbyhq.com/<slug>` or `job-boards.greenhouse.io/<slug>`. Use a prefix (`lever:<slug>`) if a slug exists on several ATS. `RUN_REPORT` lists the status of every company.

**Why is a job I expected missing?** Most often the posting does not state where you may work from (`eligibilityConfidence: null`). Enable *Include jobs with unknown location rules* to see those too.

**Can I get every job without filtering?** Yes. Leave all filters empty and you get every job with its eligibility fields.

# Actor input Schema

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

Job boards to fetch. Accepts a board slug ("gitlab"), a prefixed slug ("greenhouse:gitlab", "lever:palantir", "ashby:linear") or a board URL ("https://jobs.ashbyhq.com/linear"). Bare slugs are looked up on all three ATS.

## `useStarterList` (type: `boolean`):

Adds a curated, verified list of companies with public Greenhouse, Lever or Ashby boards to the companies above.

## `countries` (type: `array`):

Only return jobs that can be done from at least one of these countries. Leave empty to get all jobs. Worldwide jobs match every country except comprehensively embargoed ones (CU, IR, KP, SY).

## `includeUnknownEligibility` (type: `boolean`):

If a posting does not say where you may work from, it is excluded from country-filtered results by default (and not charged). Enable to include such jobs; they will have eligibilityConfidence = null.

## `minConfidence` (type: `string`):

How sure the location detection must be for a job to count as a country match. "high": stated explicitly. "medium": stated, but as a broad region (e.g. EMEA) or a single sentence in the description. "low": inferred (e.g. from office locations) or contradictory.

## `workplaceTypes` (type: `array`):

Remote only by default. Add hybrid and on-site to include office jobs in your countries; remove all entries to get every job. A job counts as remote if it offers a remote option.

## `remoteScopes` (type: `array`):

Leave empty for all.

## `keywords` (type: `array`):

Keep jobs containing at least one of these words or phrases (case-insensitive, whole words). Example: "engineer", "product manager".

## `keywordsIn` (type: `string`):

Where "Keywords" are searched. "Job title" is precise; "Job title and description" finds more jobs but also more noise. "Exclude keywords" always check only the title.

## `excludeKeywords` (type: `array`):

Drop jobs whose title contains any of these words (whole words; only the title is checked, also when keywords search the description). Example: "senior", "intern".

## `departments` (type: `array`):

Keep jobs whose department or team contains one of these words or phrases (case-insensitive, whole words). Use the department name as published, e.g. "Engineering" (not "engineer"); "IT" does not match "Recruiting".

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

Jobs without a publish date are excluded when this is set. Greenhouse jobs use the first publish date.

## `employmentTypes` (type: `array`):

Keep only these employment types. Leave empty to keep all, including jobs whose type is not stated ("unknown").

## `engagementTypes` (type: `array`):

Legal basis of the job.

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

HTML is sanitised (no scripts, styles or tracking) and safe to render.

## `maxJobs` (type: `integer`):

Stop after this many matching jobs. You are only charged for jobs in the output.

## `maxConcurrency` (type: `integer`):

Number of company boards fetched in parallel.

## Actor input object example

```json
{
  "companies": [
    "gitlab",
    "ashby:linear",
    "lever:palantir"
  ],
  "useStarterList": false,
  "countries": [],
  "includeUnknownEligibility": false,
  "minConfidence": "medium",
  "workplaceTypes": [
    "remote"
  ],
  "remoteScopes": [],
  "keywords": [],
  "keywordsIn": "title",
  "excludeKeywords": [],
  "departments": [],
  "employmentTypes": [],
  "engagementTypes": [],
  "descriptionFormat": "text",
  "maxConcurrency": 8
}
```

# Actor output Schema

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

One item per job that passed all filters, with eligible countries, remote scope and contract type.

## `runReport` (type: `string`):

JSON report with counts per company and the reason the run stopped.

# 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 = {
    "companies": [
        "gitlab",
        "ashby:linear",
        "lever:palantir"
    ],
    "workplaceTypes": [
        "remote"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("refined_liberty/remote-job-feed-geo-eligibility").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 = {
    "companies": [
        "gitlab",
        "ashby:linear",
        "lever:palantir",
    ],
    "workplaceTypes": ["remote"],
}

# Run the Actor and wait for it to finish
run = client.actor("refined_liberty/remote-job-feed-geo-eligibility").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 '{
  "companies": [
    "gitlab",
    "ashby:linear",
    "lever:palantir"
  ],
  "workplaceTypes": [
    "remote"
  ]
}' |
apify call refined_liberty/remote-job-feed-geo-eligibility --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,refined_liberty/remote-job-feed-geo-eligibility"
        }
    }
}
```

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/g22PXWShropLixQPH/builds/maWKxn6gtvMZ8dbQP/openapi.json
