# Devex Jobs Scraper — NGO & International Development Jobs (`nomad-agent/devex-scraper`) Actor

Scrape international-development, NGO, humanitarian and UN jobs from Devex.com — donor-funded programmes, consultancies, INGO and multilateral roles. An AI step (bring your own Anthropic or Mistral key) returns clean records: title, organisation, location, salary, deadline, URL.

- **URL**: https://apify.com/nomad-agent/devex-scraper.md
- **Developed by:** [Nomad.Dev](https://apify.com/nomad-agent) (community)
- **Categories:** Jobs
- **Stats:** 1 total users, 1 monthly users, 96.8% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 job results

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/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Devex Jobs Scraper — NGO & International Development Jobs

Scrape international-development, NGO, humanitarian and UN vacancies from Devex.com, parsed by an AI extraction step (BYO Anthropic or Mistral key). Up to 17 structured fields per posting, a delta/monitoring mode that pays only for genuinely new jobs, and an alive-check mode to re-verify known postings.

> **Claude / Codex skill to describe and setup this actor: [SKILL.md](https://github.com/Exdenta/OinkAIJobSearch/blob/main/skill/devex-scraper/SKILL.md)**

> **Bring your own key.** This Actor uses either Claude (Anthropic) or Mistral for AI discovery/extraction — pick with `provider`, or just supply whichever key you have (the Actor auto-selects the matching provider when `provider` is left unset). Pass `anthropicApiKey` (provider `"anthropic"`, default) or `mistralApiKey` (provider `"mistral"`) in the input. **Without a matching key the run still succeeds** — it produces a single dataset row explaining how to supply a key, instead of failing.

### AI provider: Anthropic vs Mistral

`provider` selects which AI does discovery + extraction:

- **`anthropic`** (default) — a Claude agent uses Anthropic's built-in web-search tool to search Devex via Google site-search and extract structured listings from the same tool loop.
- **`mistral`** — since Mistral has no built-in web-search tool, this path uses [keenable](https://keenable.ai) (no-auth web search) to run the same Google site-searches, then ONE Mistral call extracts structured fields from the combined candidate list (title/URL/snippet — Devex blocks direct page fetches for both providers, so both work from search-result metadata only, never a page fetch).

Pick whichever provider you'd rather bring a key for; output shape and the no-guessing extraction rules (never fabricate `postedAt`/`deadline`, extract `company` from explicit signals only) are identical either way.

### What Devex jobs data does this scraper extract?

Each result is one flat JSON record per job posting:

| Field | Meaning |
|---|---|
| `source` | Always `"devex"` |
| `id` | Stable source-side identifier (job id parsed from the posting URL) |
| `title` | Job title as posted |
| `company` | Hiring company / organisation |
| `location` | Location / duty station text from the search snippet (may be empty) |
| `url` | Direct link to the posting |
| `postedAt` | Posting date (`YYYY-MM-DD`), only when explicitly stated on the page or search snippet — `null` when unknown. Devex postings are discovered via Google search results, which frequently omit a reliable date, so `null` is common and expected; the actor never guesses or fabricates a date. |
| `deadline` | Application closing date (`YYYY-MM-DD`), only when explicitly stated — `null` when not stated. Also commonly `null`. |
| `salary` | Pay / day-rate exactly as stated (e.g. `USD 90,000–110,000`) — `null` when not stated, never guessed. |
| `contractType` | Engagement type when stated: `full-time`, `part-time`, `contract`, `permanent`, `temporary`, `internship`, `consultancy`, or `volunteer` — `null` when unclear. |
| `workplace` | `on-site`, `hybrid`, or `remote` when stated — `null` when unclear. |
| `employerWebsite` | Hiring organisation's website or Devex profile URL when present in the snippet/URL — `null` otherwise, never fabricated. |
| `snippet` | Short description excerpt from the search result |
| `isNew` | Only in delta mode (`onlyNewSinceLastRun`): always `true`, marking a posting not delivered on a prior delta run. |
| `isActive` | Only in alive-check mode (`jobUrls`): `true` if the posting still appears open, `false` if closed/expired, `null` if undeterminable. |
| `checkedAt` | Only in alive-check mode: ISO 8601 UTC timestamp of the liveness check. |

`externalId` is also present on every record, duplicating `id` — it is a deprecated alias kept for backward compatibility with existing integrations and will be removed in a future major version.

### How to scrape Devex jobs with this Actor

1. Click **Try for free** / **Run** — no login to the target site, no cookies, no proxies to configure.
2. Adjust the input (keyword, filters, `maxItems`) or keep the defaults.
3. Run it and export the dataset as JSON, CSV or Excel, or read it over the [API](https://docs.apify.com/api/v2).

Run it from your own code:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("nomad-agent/devex-scraper").call(run_input={"maxItems": 50})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item["title"], "—", item["company"], item["url"])
```

Or a single HTTP call that runs the Actor and returns items in one response:

```bash
curl -X POST \
  "https://api.apify.com/v2/acts/nomad-agent~devex-scraper/run-sync-get-dataset-items?token=<YOUR_APIFY_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"maxItems": 50}'
```

### Integrations

Export the dataset as JSON, CSV or Excel from the Apify Console, pull it over the [API](https://docs.apify.com/api/v2) (including `run-sync-get-dataset-items` for a single-call run), wire it into Make/Zapier/n8n, or call this Actor as a tool from an AI agent via the [Apify MCP server](https://apify.com/apify/actors-mcp-server).

### Input

| Field | Type | Default | Notes |
|---|---|---|---|
| `provider` | string | `"anthropic"` | Which AI provider runs discovery + extraction: `"anthropic"` (Claude + built-in web search) or `"mistral"` (keenable web search + Mistral extraction). |
| `anthropicApiKey` | string | — | Your Anthropic API key (sk-ant-...). Required when `provider` is `"anthropic"` — your key is billed directly by Anthropic for that usage (separate from Apify's pricing). Keep this secret. |
| `mistralApiKey` | string | — | Your Mistral API key. Required when `provider` is `"mistral"` — used only for extraction (search itself goes through keenable, no key needed). Billed directly by Mistral, separate from Apify's pricing. Keep this secret. |
| `mistralModel` *(Advanced)* | string | `"mistral-small-latest"` | Mistral model for extraction (provider `"mistral"` only): `mistral-small-latest` (default — matches larger models on this small, well-scoped task), `mistral-medium-latest`, or `mistral-large-latest`. |
| `keyword` | string | `""` | Topic or role keyword to focus the search (e.g. "monitoring evaluation", "migration", "policy analyst"). Leave empty to surface a broad mix of international-development postings. |
| `location` | string | `""` | Geographic filter passed to the search (e.g. "Geneva", "remote", "Africa"). Leave empty for worldwide listings. |
| `contractType` | string | `""` (Any) | Only return postings of this engagement type (`full-time`, `part-time`, `contract`, `permanent`, `temporary`, `internship`, `consultancy`, `volunteer`). Applied by the AI as a search constraint, not a post-hoc regex. |
| `workplace` | string | `""` (Any) | Only return postings with this workplace type (`on-site`, `hybrid`, `remote`). Applied by the AI as a search constraint. |
| `onlyNewSinceLastRun` | boolean | `false` | Delta / monitoring mode — output only postings not seen on a previous run with this flag on. See **Delta mode / monitoring** below. |
| `jobUrls` | array | `[]` | Alive-check mode — provide known Devex job URLs to re-verify whether each is still open (returns `isActive` per URL). When set, discovery is skipped. See **Alive-check mode** below. |
| `maxItems` | integer | `12` | Maximum number of job postings to return (1–50). In alive-check mode it also caps how many `jobUrls` are checked. Each result is billed under this Actor's pay-per-result pricing; the AI API calls behind every run are ALSO billed separately by your provider, regardless of this cap. |
| `model` *(Advanced)* | string | `"claude-haiku-4-5-20251001"` | Anthropic model to use for job discovery. Must support the web\_search\_20250305 built-in tool (Claude 3.5 Sonnet or later recommended). The default works well for almost everyone. |
| `proxyConfiguration` *(Advanced)* | object | Apify Proxy | Reserved for parity / forward-compatibility. Discovery runs server-side inside the AI provider or keenable, neither of which routes through an Apify proxy, so this setting is currently inert. |

### Output example

`postedAt` and `deadline` are `null` unless Devex's search results genuinely state a date — that's the honest common case, not a bug:

```json
{
  "source": "devex",
  "id": "889021",
  "externalId": "889021",
  "title": "Chief of Party — USAID Health",
  "company": "Chemonics",
  "location": "Nairobi, Kenya",
  "url": "https://www.devex.com/jobs/chief-of-party-889021",
  "postedAt": null,
  "deadline": null,
  "salary": null,
  "contractType": "full-time",
  "workplace": null,
  "employerWebsite": null,
  "snippet": "Chemonics seeks a Chief of Party..."
}
```

When a date, salary, or workplace is stated on the page or in the search snippet, the matching field is populated as a string instead of `null`.

### Delta mode / monitoring

Set `onlyNewSinceLastRun: true` to run this Actor on a schedule and pay only for **genuinely new** Devex postings. The Actor remembers every posting `id` it has delivered in a dedicated per-Actor key-value store; on the next delta run, already-seen postings are dropped **before** they are pushed or billed. New postings carry `"isNew": true`.

- The **first** delta run has nothing to compare against, so it emits everything and seeds the state.
- State is best-effort: if the state store can't be read, the run safely treats everything as new rather than failing.
- Combine with `keyword`/`location`/`contractType`/`workplace` to monitor a specific slice (e.g. new remote M\&E consultancies) cheaply on a cron schedule.

### Alive-check mode

Pass a list of known Devex job URLs in `jobUrls` to re-verify whether each posting is still open — useful for keeping an existing pipeline/ATS free of dead links. When `jobUrls` is set, the Actor skips keyword/location discovery and returns **one row per URL** with:

- `isActive`: `true` (still open), `false` (closed/expired/removed), or `null` (could not be determined).
- `checkedAt`: ISO 8601 UTC timestamp of the check.

Because Devex blocks direct page fetches, liveness is judged from live web-search signals rather than a page load, so `null` is returned honestly when the signal is ambiguous rather than guessing.

### Pricing

Pay per event: **$0.005 per Actor start** and **$0.003 per job returned**.
100 jobs ≈ $0.305. No subscription, no rental — you pay only for what you fetch.

**This is a "bring your own key" (BYOK) Actor.** Every run calls the AI provider you pick (`provider`) using the `anthropicApiKey` or `mistralApiKey` you supply, and that usage is billed to **your own Anthropic or Mistral account, separately from Apify's pricing above**. Apify's PPE charges only cover the Actor's own run/result events — they do not include Anthropic's or Mistral's token costs. Check [Anthropic's pricing](https://www.anthropic.com/pricing) or [Mistral's pricing](https://mistral.ai/pricing) for the model you configure.

### Use cases

- Development-sector job boards
- Proposal/recruitment teams tracking donor-funded roles
- Consultant roster building
- Aid-sector hiring analysis

### FAQ

**Is it legal to scrape Devex jobs?**
This Actor reads only publicly available job postings — data any visitor can see without logging in. No personal data behind authentication is touched. Review the target site's terms and your local regulations for your specific use case.

**Do I need an account on the target site?**
No. Postings are fetched from public pages/APIs — no login, cookies or session tokens.

**How fresh is the data?**
Every run performs live Claude web searches against Devex's Google-indexed listings at run time — there is no caching layer, so each run reflects whatever is currently indexed.

**How many jobs can I get?**
`maxItems` caps the run at 1–50 postings.

**Something broken or missing?**
Open an issue on the Actor's **Issues** tab — it is monitored and reliability fixes ship fast.

### Related Actors

- [UNjobs Scraper — unjobs.org UN Vacancies](https://apify.com/nomad-agent/unjobs-scraper)
- [Research & Academic Jobs Scraper — 10 Sources](https://apify.com/nomad-agent/researcher-bundle)
- [UN Careers Job Scraper — United Nations Jobs](https://apify.com/nomad-agent/un-careers-scraper)
- [ReliefWeb Jobs Scraper — Humanitarian & NGO](https://apify.com/nomad-agent/reliefweb-scraper)
- [Impactpool Jobs Scraper — UN & NGO Careers](https://apify.com/nomad-agent/impactpool-scraper)

***

**From the maker of [Oink](https://github.com/Exdenta/OinkAIJobSearch)** — an open-source, AI-powered job-search bot for Telegram that runs on these Actors. [Try the free bot](https://t.me/job_search_everyday_bot), get a managed instance at [oinkjobsearch.com](https://oinkjobsearch.com), or browse the [full catalog of 50+ Actors](https://apify.com/nomad-agent).

# Actor input Schema

## `provider` (type: `string`):

Which AI provider runs discovery + extraction. <code>anthropic</code> (default) uses a Claude agent with Anthropic's built-in web-search tool. <code>mistral</code> uses keenable (no-auth web search) for discovery plus a Mistral model to extract structured fields from the search results -- pick this if you'd rather bring a Mistral key than an Anthropic one. <code>openai</code> uses the same keenable search plus a GPT model to extract structured fields -- pick this if you'd rather bring an OpenAI key.

## `anthropicApiKey` (type: `string`):

Your Anthropic API key (sk-ant-...). Required when provider is <code>anthropic</code> — the actor uses Claude with web search to discover DevEx listings, and your key is billed directly by Anthropic for that usage (separate from Apify's pricing). Keep this secret.

## `mistralApiKey` (type: `string`):

Your Mistral API key. Required when provider is <code>mistral</code>. Used only for extraction from search results -- the web search itself goes through keenable, which needs no key. Billed directly by Mistral for that usage (separate from Apify's pricing). Keep this secret.

## `mistralModel` (type: `string`):

Mistral model for extraction (provider=<code>mistral</code> only). Small is the default -- testing found it matches larger Mistral models on this task (extraction is small and well-scoped: title/company/location/dates from a search snippet) at a fraction of the cost.

## `openaiApiKey` (type: `string`):

Your OpenAI API key (sk-...). Required when provider is <code>openai</code>. Used only for extraction from search results -- the web search itself goes through keenable, which needs no key. Billed directly by OpenAI for that usage (separate from Apify's pricing). Keep this secret.

## `openaiModel` (type: `string`):

OpenAI model for extraction (provider=<code>openai</code> only). Defaults to <code>gpt-4.1-mini</code> -- cheap, fast and ample for this well-scoped extraction task. Any chat-completions model works, including the gpt-5 family (e.g. <code>gpt-5.4-mini</code>).

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

Topic or role keyword to focus the search (e.g. <code>monitoring evaluation</code>, <code>migration</code>, <code>policy analyst</code>). Leave empty to surface a broad mix of international-development postings.

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

Geographic filter passed to the search (e.g. <code>Geneva</code>, <code>remote</code>, <code>Africa</code>). Leave empty for worldwide listings.

## `contractType` (type: `string`):

Only return postings of this engagement type. Applied by the AI as a search constraint (not a post-hoc regex), so it works on either provider. Leave as <code>Any</code> for all types. Devex is consultancy-heavy, so <code>consultancy</code> is included.

## `workplace` (type: `string`):

Only return postings with this workplace type. Applied by the AI as a search constraint (not a post-hoc regex). Leave as <code>Any</code> for all.

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

Delta / monitoring mode: only output postings not delivered on a previous run that also had this flag on. Already-seen postings are dropped before push (never billed), so this is the cheapest way to run this Actor on a schedule and pay only for genuinely new Devex vacancies. State is tracked per Actor in a dedicated key-value store, keyed by each posting's <code>id</code>. The first delta run has nothing to compare against, so it emits everything. See README “Delta mode / monitoring”.

## `jobUrls` (type: `array`):

Alive-check mode (Idealist-style): paste known <code>https://www.devex.com/jobs/...</code> URLs to re-verify whether each posting is still open. When set, the Actor skips keyword/location discovery and instead returns one row per URL with an <code>isActive</code> verdict (true / false / null when it cannot be determined). Leave empty for normal discovery. Note: Devex blocks direct page fetches, so liveness is judged from live web-search signals, not a page load.

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

Maximum number of job postings to return (1–50). Each result is billed under this Actor's pay-per-result pricing; the Claude API calls behind every run are ALSO billed separately by Anthropic to your own account, regardless of this cap.

## `model` (type: `string`):

Anthropic model to use for job discovery. Must support the web\_search\_20250305 built-in tool (Claude 3.5 Sonnet or later recommended). Advanced setting — the default works well for almost everyone.

## `proxyConfiguration` (type: `object`):

Reserved for parity / forward-compatibility. Discovery runs server-side inside the AI provider (Claude's built-in web search) or keenable, neither of which routes through an Apify proxy, so this setting is currently inert — there is no direct HTTP fetch to Devex to proxy. Safe to leave at the default.

## Actor input object example

```json
{
  "provider": "anthropic",
  "mistralModel": "mistral-small-latest",
  "openaiModel": "gpt-4.1-mini",
  "keyword": "research officer",
  "location": "Geneva",
  "contractType": "",
  "workplace": "",
  "onlyNewSinceLastRun": false,
  "jobUrls": [],
  "maxItems": 12,
  "model": "claude-haiku-4-5-20251001",
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

No description

# 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 = {
    "jobUrls": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("nomad-agent/devex-scraper").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 = { "jobUrls": [] }

# Run the Actor and wait for it to finish
run = client.actor("nomad-agent/devex-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "jobUrls": []
}' |
apify call nomad-agent/devex-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=nomad-agent/devex-scraper",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/ZutM3sroBQcWaeA0d/builds/Vp1X6Sfei3P4OXpmu/openapi.json
