# Workable Jobs Lookup — Company Job Board API (`accountable_eel/workable-jobs-lookup`) Actor

Look up any company's live job openings on Workable by account slug or careers-page URL. Returns job title, department, location, remote status, employment type, and apply link for every open posting, pulled from Workable's own public widget API. Billed only when open jobs are found.

- **URL**: https://apify.com/accountable\_eel/workable-jobs-lookup.md
- **Developed by:** [Adrian Voss](https://apify.com/accountable_eel) (community)
- **Categories:** Jobs, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 1 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 successful lookups

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?

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

## Workable Jobs Lookup — Company Job Board API

Paste a list of companies and get every open role on each one's [Workable](https://www.workable.com)
job board — title, department, location, employment type, remote flag and a direct apply link.
This calls Workable's own public job board API directly: no API key, and no scraping of the
rendered careers page.

### Who it's for

The accountable\_eel catalogue sells company intelligence columns for outbound. Each actor takes a
list of domains or company identifiers and returns one flat, stably-named row per input —
firmographics, registry IDs, tech stack, email route, hiring activity — the shape a Clay table, an
n8n workflow, or an AI agent can consume without post-processing. Pricing is pay-per-event and
per-domain: a few tenths of a cent for a row that was actually found, and nothing for a
miss, so a list that doesn't enrich costs you next to nothing. Where an official source exists —
VIES, GLEIF, SEC EDGAR, Brønnøysund, PRH, RDAP — it is queried directly instead of scraped. No
seat licence, no monthly minimum, no credit system to decode. Here the "identifier" is a Workable
account slug or careers URL, and the "row" is a job posting.

### Why this one

- **You pay per company, not per job.** A board with 300 open roles and a board with 2 both cost
  one lookup. Competing Workable actors bill per posting, which makes a large employer far more
  expensive.
- **Direct from Workable's own API.** The same source their careers pages read — no HTML
  selectors to break on a redesign.
- **Real filters, not a raw dump.** Title keyword include/exclude, location, remote-only and
  posted-after all combine, so you ask for the roles you want instead of filtering afterwards.
- **A delta mode for watching hiring.** Turn on `onlyNewSinceLastRun`, put it on a schedule, and
  each run returns only postings you haven't seen — with a heartbeat row on a quiet week.
- **Slug or URL.** A bare account slug, an `apply.workable.com/<company>/` URL, or a
  `<company>.workable.com` URL all resolve.

### What you get

One row per open posting by default, or one row per company with the roles nested in `jobs`.
The columns never move, whether or not filters or delta mode are on.

| Field | Type / format | Description |
|---|---|---|
| `query` | text | The company value you passed in, unchanged. |
| `found` | boolean | `true` if the company resolved to at least one open posting. Gates billing. |
| `status` | text | `OK`, `NOT_FOUND` (no board or no open roles), or `BAD_FORMAT`. |
| `account` | text | The Workable account slug, extracted from a slug or a careers URL. |
| `companyName` | text | Company name as Workable publishes it. |
| `jobCount` | number | Postings left in this row after filters and delta filtering. |
| `totalJobCount` | number | Postings on the board before filtering — shows what a filter dropped. |
| `newJobs` | number | Postings new since your last run. Empty unless `onlyNewSinceLastRun` is on. |
| `firstRun` | boolean | `true` on the baseline run for a watchlist. Empty unless delta mode is on. |
| `jobs` | array | All matching postings, each with the fields below. |
| `id` | text | Workable's posting id. |
| `title` | text | Job title. |
| `department` | text | Department as the company labels it. |
| `location` | text | Office or region as Workable lists it. |
| `remote` | boolean | `true` when the posting is flagged remote, or the location or title reads as remote. |
| `employmentType` | text | Full-time, part-time, contract, and so on. |
| `publishedOn` | date (ISO 8601) | Workable's own publish date for the posting. |
| `postedAt` | date (ISO 8601) | The catalogue-wide name for the same moment, so a query written against one ATS actor keeps working against another. |
| `applyUrl` | link | Direct apply link for the role. |
| `scrapedAt` | date (ISO 8601) | When this actor fetched the row. |

### Price

$5 per 1,000 companies, plus a $0.005 start fee. Misses (`found:false`) are never charged.

You're billed once per company that resolves to at least one open posting, however many roles
that company has. A company that isn't on Workable, or has nothing open, costs nothing. Run
1,000 companies and, at the live FREE-tier price, that's roughly **$5 if every one is found** —
less as some miss, and less again on paid Apify plans. Because billing is per company rather
than per posting, a list of large employers costs the same here as a list of small ones.

### How to use

1. **In the Apify Console.** Open the actor page and click **Start** — the `companies` field is already pre-filled with a working example. Results land in the run's dataset as soon as each item is found.
2. **Via the API.** Call it directly with a POST request — no Console needed once you have an API token:
   ```bash
   curl "https://api.apify.com/v2/acts/accountable_eel~workable-jobs-lookup/run-sync-get-dataset-items?token=<YOUR_TOKEN>" \
     -X POST \
     -H "Content-Type: application/json" \
     -d '{"companies":["getresponse"]}'
   ```
3. **On a schedule.** Save this actor as an Apify **Task** with the input you want, then add a **Schedule** (hourly, daily, weekly) so it runs on its own — no server of your own required.

1) Paste one company per line — the account slug, or a full Workable careers URL.
2) Run the actor. Each company's board is fetched, in parallel up to `maxConcurrency`.
3) Read the dataset. Each row carries the postings, or `found: false` if nothing is open.
4) Turn on `expandRows` for one row per posting, or leave it off to keep roles nested per
   company.

**Common ways teams use this:**

- **Track hiring velocity** at target companies by re-running on a schedule and diffing
  `jobCount` over time.
- **Build a sourcing pipeline** that pulls fresh roles with department, location and apply URL
  for a watchlist of employers.
- **Feed a sales-intelligence dashboard** with headcount-growth signal by department or location.
- **Research who's hiring in a market** by combining `locations` with `titleKeywords`.
- **Confirm a company still uses Workable** before building an integration around it.

### Input

```json
{
  "companies": [
    "getresponse"
  ]
}
```

One company per line — paste the Workable account slug, a full apply.workable.com URL, or a <company>.workable.com URL. Accepted formats: getresponse, https://apply.workable.com/netguru/.

To narrow what comes back, open the **🎯 Job filters** section. All are optional, and they
combine with AND across fields, OR within a field:

| Input | What it does |
| --- | --- |
| `titleKeywords` | Keep only roles whose title contains one of these — `["engineer","designer"]`. Case-insensitive, partial words count. |
| `excludeTitleKeywords` | Drop roles whose title contains one of these — `["intern","contract"]`. Applied after `titleKeywords`. |
| `locations` | Keep only roles whose location contains one of these — `["Berlin","Remote"]`. |
| `remoteOnly` | Keep only roles that read as remote. |
| `postedAfter` | Keep only roles published on or after this date — `"2026-06-01"`. Inclusive. |

Leave every filter empty and you get every open role. `jobCount` (matched) and `totalJobCount`
(before filtering) both come back on every row, so a filtered result is never ambiguous — and a
company whose roles all get filtered out still returns a `found: true` row with `jobCount: 0`,
charged like any other found company.

For a standing watchlist, turn on **`onlyNewSinceLastRun`**. The first run baselines and sets
`firstRun: true`; every run after returns only postings it hasn't shown you, counted in
`newJobs`. A quiet run still returns a heartbeat row so you can tell "nothing new" from "the run
broke". The seen-list lives in a key-value store in your own account; changing a filter starts a
fresh watchlist automatically.

### Sample output

| query | found | status | account | companyName | jobCount | totalJobCount | newJobs | firstRun | jobs | id | title | department | location | remote | employmentType | publishedOn | postedAt | applyUrl | scrapedAt |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| getresponse | true | OK | getresponse | GetResponse | 5 | 5 |  |  | \<all jobs (full list)> | BD19056FD5 | Accountant | Business Operations | Poland | true | Full-time | 2026-08-10 | 2026-08-10 | https://apply.workable.com/j/BD19056FD5 | 2026-08-26T06:00:53.713Z |

That table lists every available column. With `expandRows` on, a real row reads like this:

```json
{
  "query": "getresponse",
  "found": true,
  "status": "OK",
  "account": "getresponse",
  "companyName": "GetResponse",
  "jobCount": 14,
  "totalJobCount": 14,
  "id": "A1B2C3D4E5",
  "title": "Senior Backend Engineer",
  "department": "Engineering",
  "location": "Gdańsk, Poland",
  "remote": false,
  "employmentType": "Full-time",
  "publishedOn": "2026-08-11T00:00:00Z",
  "postedAt": "2026-08-11T00:00:00Z",
  "applyUrl": "https://apply.workable.com/getresponse/j/A1B2C3D4E5/",
  "scrapedAt": "2026-08-24T06:01:11.918Z"
}
```

A company that isn't on Workable, or has nothing open, comes back as a miss and is never charged:

```json
{
  "query": "not-a-real-company",
  "found": false,
  "status": "NOT_FOUND",
  "scrapedAt": "2026-08-24T06:01:11.918Z"
}
```

Miss rows are still written so you can see exactly which companies failed rather than diffing
your input list against the output. Turn on `onlyFound` to drop them.

### Use it from Clay, n8n, Make, or an AI agent

This actor runs synchronously over plain HTTP — call it directly from a script, a workflow tool, or an AI agent, no Apify Console needed once you have an API token.

```bash
curl "https://api.apify.com/v2/acts/accountable_eel~workable-jobs-lookup/run-sync-get-dataset-items?token=<YOUR_TOKEN>" \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"companies":["getresponse"]}'
```

**n8n.** Add an HTTP Request node: Method `POST`, URL `https://api.apify.com/v2/acts/accountable_eel~workable-jobs-lookup/run-sync-get-dataset-items?token=<YOUR_TOKEN>`, Body Content Type `JSON`, JSON Body `{"companies":["getresponse"]}` (swap in an expression from an earlier node for a real value).

**Clay.** Add an "HTTP API" column: Method `POST`, URL `https://api.apify.com/v2/acts/accountable_eel~workable-jobs-lookup/run-sync-get-dataset-items?token=<YOUR_TOKEN>`, Body `{"companies":["{{company}}"]}`, mapping the row's company into the `companies` array.

**MCP.** In Claude, Cursor, or any MCP client with the Apify MCP server, ask for "Workable Jobs Lookup | Apify" — the agent will find and run this actor.

### Tips

- **Paste the careers URL rather than guessing the slug.** The account slug isn't always the
  company name, and passing the URL extracts it correctly for you.
- **Use `totalJobCount` to sanity-check a filter.** If `jobCount` is 0 but `totalJobCount` is 40,
  your filter is too narrow rather than the company having stopped hiring.
- **Don't combine delta mode with "Only keep rows containing."** That filter runs on the
  finished row and can drop the `newJobs: 0` heartbeat — the row that proves the schedule ran.
- **Use one-row-per-company mode for a hiring signal.** Read `jobCount` per company without
  pulling every posting into your table.
- **`postedAt` and `publishedOn` are the same moment.** Use `postedAt` if you're writing one
  query across several ATS actors in this catalogue.

### vs. alternatives

| | What it costs | What you get | Trade-off |
|---|---|---|---|
| **This actor** (`workable-jobs-lookup`) | $5 per 1,000 companies found (less on paid tiers), $0.005 actor start, nothing for a company with no open roles | Every open posting on a company's board — title, department, location, employment type, apply URL — filtered by keyword, location or date, with a delta mode for watchlists | Workable only. If your list is mixed-ATS, use `ats-jobs-unified-lookup`. |
| **shahidirfan/Workable-Job-Scraper** (59 users) | $0.001 per result, $0.005 start | Per-posting billing across Workable boards | Cheaper on a small board, more expensive on a large one — a 300-role employer costs about $0.30 there versus one company charge here. |
| **jobo.world/workable-jobs-scraper-api** (50 users) | $0.004 per job | Per-job billing with a similar field set | Four times the per-posting price of the actor above, and far more expensive than per-company billing on any sizeable board. |
| **Workable's own API** | Free | The same JSON | It is the same JSON. This handles the slug-versus-URL extraction, the filters, the delta state and the retries, and hands you a flat table. |

Competitor prices are their published FREE-tier list prices as of August 2026 and are not
tracked here — check the actor's own page before relying on the comparison.

### FAQ

**What counts as "not found"?** Either the company has no Workable board, or it has one with zero
open postings. Both come back `found: false` and are never charged.

**How do I find a company's Workable slug?** It's the path segment in
`apply.workable.com/<slug>/`. Pasting the full URL avoids having to identify it yourself.

**Why was I charged for a company that returned 0 jobs?** Because its board had open postings —
they just didn't match your filters, or weren't new since your last run. `totalJobCount` shows
what was there before filtering. A company with no board at all is `found: false` and free.

**Does the delta state cost anything or leak between users?** It lives in a named key-value store
inside your own Apify account, so nobody else can see it, and it holds only posting ids per
company. Delete that store to reset every watchlist.

**Does this only cover companies using Workable?** Yes. A company on Greenhouse, Lever, Workday
or another ATS won't resolve here — use `ats-jobs-unified-lookup` if your list is mixed.

**Do closed or filled positions show up?** No. The API returns currently open postings only.

**Does it require authentication or a proxy?** Neither. Workable's job board API is public and
unauthenticated, and this reaches it without a proxy. `proxyConfiguration` is exposed if your own
network policy needs it.

**Can an AI agent call this directly?** Yes — it's registered with the Apify MCP server. An agent
with that MCP connected can find and run "Workable Jobs Lookup" by name and get dataset rows back
with no integration code.

### Related actors

- [ATS Jobs Unified Lookup](https://apify.com/accountable_eel/ats-jobs-unified-lookup) — check a
  company across Workable, Greenhouse, Lever, Ashby and more in one call when you don't know
  which ATS they use.
- [Greenhouse Jobs Lookup](https://apify.com/accountable_eel/greenhouse-jobs-lookup) — the same
  lookup, filters and delta mode for companies on Greenhouse.
- [SmartRecruiters Jobs Lookup](https://apify.com/accountable_eel/smartrecruiters-jobs-lookup) —
  the same lookup for companies on SmartRecruiters.

# Actor input Schema

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

One company per line — paste the Workable account slug, a full apply.workable.com URL, or a <company>.workable.com URL. Accepted formats: getresponse, https://apply.workable.com/netguru/. You're only charged for the ones we actually find — a miss costs nothing.

## `testRun` (type: `boolean`):

Turn this on to test your input on a small sample before running the full list. Turn it off to process everything.

## `onlyFound` (type: `boolean`):

Only keep rows where something was actually found. Misses are always free, whether or not you show them here.

## `includeKeywords` (type: `array`):

Optional. Only keep results that mention at least one of these words (e.g. a job title, a city, a product name). Leave empty to keep everything.

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

Optional. Drop any result that mentions one of these words. Leave empty to skip nothing.

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

Optional. Stop the run once this many results have been found — useful for a quick, cheap sample. Leave blank for no limit.

## `titleKeywords` (type: `array`):

Optional. Keep only roles whose title contains at least one of these words — e.g. "engineer", "account executive", "designer". Case doesn't matter, and partial words work ("engineer" matches "Engineering Manager"). Leave empty to keep every role.

## `excludeTitleKeywords` (type: `array`):

Optional. Drop any role whose title contains one of these words — e.g. "intern", "senior", "contract". Applied after the include list above, so a role matching both is dropped.

## `locations` (type: `array`):

Optional. Keep only roles whose location mentions one of these — e.g. "Berlin", "New York", "United Kingdom". Case doesn't matter and partial matches work. Leave empty to keep every location.

## `remoteOnly` (type: `boolean`):

Keep only roles whose location or title says remote (also matches "anywhere", "work from home", "distributed"). Companies label remote work inconsistently, so treat this as a strong filter, not a guarantee.

## `postedAfter` (type: `string`):

Optional. A date like 2026-06-01 (or a full timestamp). Keeps only roles Workable published on or after this date. Leave empty for no date limit.

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

Turn this on for a scheduled watchlist: the first run returns everything that matches and remembers it, and every run after that returns only postings it hasn't shown you before. A company with nothing new still gets a row (with 0 jobs), so a quiet schedule never looks like a broken one. Changing the filters above starts a fresh watchlist.

## `columns` (type: `array`):

Choose which pieces of information to include in each result row. All are included by default.

## `expandRows` (type: `boolean`):

When on, each job posting found gets its own row instead of being grouped under its company. You're still only charged once per company, no matter how many rows it produces.

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

Parallel requests. Keep conservative — this target has no browser fallback, so getting blocked costs more than slow-and-steady.

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

Apify Proxy config. Residential recommended for anti-bot-sensitive targets.

## Actor input object example

```json
{
  "companies": [
    "getresponse"
  ],
  "testRun": false,
  "onlyFound": false,
  "includeKeywords": [],
  "excludeKeywords": [],
  "titleKeywords": [],
  "excludeTitleKeywords": [],
  "locations": [],
  "remoteOnly": false,
  "postedAfter": "",
  "onlyNewSinceLastRun": false,
  "columns": [
    "account",
    "companyName",
    "jobCount",
    "totalJobCount",
    "newJobs",
    "firstRun",
    "jobs",
    "id",
    "title",
    "department",
    "location",
    "remote",
    "employmentType",
    "publishedOn",
    "postedAt",
    "applyUrl"
  ],
  "expandRows": true,
  "maxConcurrency": 5,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `results` (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 = {
    "companies": [
        "getresponse"
    ],
    "includeKeywords": [],
    "excludeKeywords": [],
    "titleKeywords": [],
    "excludeTitleKeywords": [],
    "locations": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("accountable_eel/workable-jobs-lookup").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": ["getresponse"],
    "includeKeywords": [],
    "excludeKeywords": [],
    "titleKeywords": [],
    "excludeTitleKeywords": [],
    "locations": [],
}

# Run the Actor and wait for it to finish
run = client.actor("accountable_eel/workable-jobs-lookup").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": [
    "getresponse"
  ],
  "includeKeywords": [],
  "excludeKeywords": [],
  "titleKeywords": [],
  "excludeTitleKeywords": [],
  "locations": []
}' |
apify call accountable_eel/workable-jobs-lookup --silent --output-dataset

```

## MCP server setup

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

```

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/8LIaijewEpaLTppfW/builds/uoI2Yve7sgXs1tdRl/openapi.json
