# Workable, Recruitee, Personio & Teamtailor Jobs API (`atarivo/ats-jobs-api-workable-recruitee-personio-teamtailor`) Actor

Every open job on the Workable, Recruitee, Personio and Teamtailor career sites you list, one row per job: title, company, location, remote flag, department, posted date, apply URL, ATS and dedup key; description on request. Company names or careers URLs in. Public feeds only, no login.

- **URL**: https://apify.com/atarivo/ats-jobs-api-workable-recruitee-personio-teamtailor.md
- **Developed by:** [Atarivo Enterprises Inc.](https://apify.com/atarivo) (community)
- **Categories:** Jobs, Lead generation, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 job scrapeds

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

Workable, Recruitee, Personio, Teamtailor & SmartRecruiters Jobs API returns every open job on
the company career sites you list, as clean rows with the same fields whichever of these five
applicant tracking systems (ATS) the company uses: title, company, location, remote flag, department,
posted date, apply URL, ATS and a dedup key. It is built for job aggregators, recruiters,
labour-market researchers and AI agents that need fresh European and global postings straight
from the source. To try it, keep the sample input and press **Start**. The sample reads one
public career site per ATS and finishes in a few seconds.

### What does the Workable, Recruitee, Personio, Teamtailor and SmartRecruiters Jobs API do?

- Returns **one dataset row per open job** in one normalised schema, so Workable jobs,
  Recruitee jobs, Personio jobs, Teamtailor jobs and SmartRecruiters jobs go into the same
  table without any mapping. The schema is the same as our Greenhouse, Lever & Ashby Actor's,
  so you can merge both. SmartRecruiters rows also carry `employment_type` and
  `experience_level`.
- Accepts **company names or careers URLs**: `apply.workable.com/<account>`,
  `<company>.recruitee.com`, `<company>.jobs.personio.de`, `<company>.teamtailor.com`,
  `jobs.smartrecruiters.com/<identifier>` (or `careers.smartrecruiters.com/<identifier>`), the
  matching feed URLs, `ats:name` (e.g. `recruitee:acme`, `smartrecruiters:Acme`), or a bare
  name that it tries on Workable, then Recruitee, then Personio, then Teamtailor. A bare name
  is not tried on SmartRecruiters, because its API answers an unknown company with an empty
  list instead of "not found": give `smartrecruiters:<identifier>` or the URL.
- Optionally adds the **job description as plain text** (`includeDescription`).
- **Removes duplicates** within a run by `dedup_key` (`ats:job_id`), so the same site listed
  twice is charged once per job.
- Calls **only the vendors' public job-feed hosts**: `apply.workable.com`,
  `api.smartrecruiters.com`, and one company subdomain of `recruitee.com`, `jobs.personio.de`
  or `teamtailor.com` per site. It sends an honest User-Agent that names this Actor, reads
  each host's robots.txt (and obeys it everywhere except `api.smartrecruiters.com`, see "Is it
  legal?"), honours `Crawl-delay`, sends at most 2 requests per second per vendor, and retries
  a failed request once. One request per career site in most cases (SmartRecruiters: one per
  100 jobs, plus one per job when you ask for descriptions).
- **No login, no credential, no circumvention.** No account, API key, cookie or proxy; no
  CAPTCHA solving; one honest User-Agent that never changes. If a vendor refuses a request
  (HTTP 403 or 429), the site gets an uncharged error row and the Actor does not try another
  route. It reads job postings that the employers publish for anyone to see, and does not look
  for or extract recruiter or candidate contact details; a description is returned as the
  employer published it.
- Does **not** read BambooHR, Workday, Indeed, LinkedIn,
  `jobs.workable.com` (Workable's cross-employer job search) or any other aggregator. Those
  inputs are refused with a clear message before anything is fetched or charged.

### How to use the Workable, Recruitee, Personio, Teamtailor and SmartRecruiters jobs API

1. Put one career site per line in **Career sites**. Copy the address from the company's
   careers page, or type the company name. Up to 200 sites per run.
2. Optionally tick **Include the job description text**, and set **Max jobs per career site**
   (default 1,000).
3. Press **Start**. Open the **Output** tab for the jobs table, or read the dataset through the
   API in JSON, CSV or Excel. The `RUN_SUMMARY` record in the key-value store says, per site,
   which ATS answered and how many jobs were found.

Calling it from an AI agent: `{"boards": ["workable:acme", "https://acme.teamtailor.com"]}`. The
description above is also the Actor's tool description in the Apify MCP server.

Run it on a schedule and compare `dedup_key` values between runs to see which jobs are new and
which were closed.

### Pricing

| Event | What you pay for | Price |
|---|---|---|
| `job-scraped` | One job returned as a row | $0.0020 |

Each run also has a start fee of $0.001. A career site that does not exist (HTTP 404 on every
ATS tried) or that did not answer after a retry costs one `job-scraped` event for its error
row. A site skipped because of robots.txt, or a response this Actor could not read, is never
charged. Duplicates are never charged. Set **Max cost per run** to cap a run; the Actor stops
cleanly at the cap and says so in its status message. Platform usage is included (no "+usage"
charges).

### Input

| Field | Type | Required | Notes |
|---|---|---|---|
| `boards` | array of strings | yes | 1 to 200 entries: careers URLs, `ats:name`, or bare names (not tried on SmartRecruiters). Aggregator, BambooHR, Workday and other ATS URLs are refused, and so are Teamtailor career sites on a company's own domain (use `<company>.teamtailor.com`). |
| `includeDescription` | boolean | no | Default `false`. Adds `description_text`. Same price. |
| `maxJobsPerBoard` | integer | no | 1 to 10,000; default 1,000. Caps the jobs (and the cost) per site. |

```json
{
  "boards": ["https://personio-gmbh.jobs.personio.de", "https://career.teamtailor.com",
             "https://kaizo.recruitee.com", "https://apply.workable.com/blueground",
             "https://jobs.smartrecruiters.com/BoschGroup"],
  "includeDescription": false,
  "maxJobsPerBoard": 5
}
```

### Output

One row per job:

```json
{
  "title": "Senior Data Engineer",
  "company": "Acme",
  "location": "Amsterdam, Noord-Holland, Netherlands",
  "remote": false,
  "department": "Engineering",
  "posted_at": "2026-10-01T14:30:00Z",
  "apply_url": "https://acme.recruitee.com/o/senior-data-engineer/c/",
  "ats": "recruitee",
  "board": "acme",
  "job_id": "1000001",
  "dedup_key": "recruitee:1000001"
}
```

`remote` is `true` or `false` when the ATS says so and `null` when it does not. `posted_at` is
ISO 8601 in UTC. `company` is the name the feed gives (Workable account name, Recruitee
company name, Personio subcompany, Teamtailor hiring organization, SmartRecruiters company
name), else the site name. Teamtailor's public feed has no department, so `department` is
`null` for Teamtailor rows. `employment_type` (e.g. "Full-time") and `experience_level` (e.g.
"Mid-Senior Level") come from SmartRecruiters only and are absent from other rows. A
SmartRecruiters company identifier that does not exist gives no rows (the API answers it with
an empty list), so check the identifier in `jobs.smartrecruiters.com/<identifier>`.
A site that failed gives one row with `error`, `message`, `tried` and `charged` instead. Run
storage is kept for your plan's data-retention period, then deleted by Apify.

### Other Actors by Atarivo

Every Atarivo Actor follows the same rules: limited permissions, no "+usage" pricing, charges
only after results are saved, and at least one result row per run.

- **[ATS Jobs API: Greenhouse, Lever & Ashby](https://apify.com/atarivo/ats-jobs-api-greenhouse-lever-ashby)**: the same job rows from Greenhouse, Lever and Ashby job boards, at the same price. Use both to cover most startup and scale-up career sites.
- **[PDF, Image & Word to Text and Markdown (OCR)](https://apify.com/atarivo/pdf-image-text-markdown-extractor)**: returns the text, Markdown, tables and page facts of a PDF, PNG, JPEG, TIFF or Word .docx file, one dataset row per page, plus RAG chunks with page spans. OCR runs only on scanned or unreadable pages. First page free.

### FAQ, disclaimers and support

**Is it legal?** It reads only the public, unauthenticated job feeds that each vendor provides
for showing a company's open jobs, with rate limits, and robots.txt honoured everywhere except
the one host explained below (all read on 2026-10-30/31). The hard rule: **no login, no
credential, no circumvention.** A vendor's terms and robots.txt are weighed per source as a
recorded risk, not ignored:

- **Workable**: `GET https://apply.workable.com/api/v1/widget/accounts/<account>[?details=true]`.
  Workable's help article "Using the Workable API to create a careers page" lists this as a
  public endpoint: "to get the list of your published jobs only, you can try in your terminal
  the below public endpoints: ... `https://www.workable.com/api/accounts/{subdomain}?details=true`"
  (that address redirects to the widget endpoint above). robots.txt of `apply.workable.com`
  disallows nothing.
- **Recruitee**: `GET https://<company>.recruitee.com/api/offers/`. Recruitee's Careers Site
  API docs: "The Recruitee Careers Site API allows to view company's jobs ... Common usage of
  this API would be, for example, to display a list of job offers".
- **Personio**: `GET https://<company>.jobs.personio.de/xml`. Personio's developer docs
  ("Retrieving open positions"): "Current open job postings can be retrieved in XML format
  under myaccount.jobs.personio.de/xml." robots.txt: `Allow: /`.
- **Teamtailor**: `GET https://<company>.teamtailor.com/jobs.json`, the career site's public
  job feed in JSON Feed 1.1 format (the feed names itself in `feed_url`; Teamtailor's help
  centre documents the same feed as `/jobs.rss`). robots.txt allows `/jobs.json`.
- **SmartRecruiters**: `GET https://api.smartrecruiters.com/v1/companies/<identifier>/postings?limit=100&offset=N`
  (and `/postings/<id>` for a description). SmartRecruiters' developer docs ("Posting API"):
  "Posting API allows for access to a job posting that was previously made public using
  SmartRecruiters application. Whenever a smartrecruiters.com user posts a job, a posting
  would become accessible via the Posting API." No key is needed for public postings, and this
  Actor sends none. robots.txt of `api.smartrecruiters.com` reads `User-agent: LinkedInBot /
  Allow: /v1/companies/` then `User-agent: * / Disallow: /`. **This Actor treats that file as
  advisory for this host only:** it is written for search-engine crawlers, while the same host
  publishes and documents this API for exactly this use (listing a company's public jobs). The
  Actor still fetches robots.txt on every run, records its verdict in the run summary
  (`robots_advisory_hosts`) and the log, and keeps the same rate limit and honest User-Agent.

The vendors' terms and conditions are contracts with their customers (the employers); none of
them forbids reading these public feeds. Workable and Teamtailor publish the robots.txt
content signal `ai-train=no`: do not use their rows to train AI models. You are responsible for
how you use the data, including any terms of the employers whose postings you republish.

**Why no BambooHR or Workday?** BambooHR's terms of service forbid using "any robot, spider,
other automated device, or manual process to monitor or copy any content from the Service",
and its careers JSON is undocumented. Workday's terms were read on 2026-10-30 and Workday stays
out. Both are refused before anything is fetched or charged.

**Why is my career site "not found"?** Check the company name in its careers address. A
Teamtailor site on the company's own domain must be given as `<company>.teamtailor.com`.

**Why are some fields null?** The ATS did not publish them for that job.

**Is it affiliated with Workable, Recruitee, Personio, Teamtailor or SmartRecruiters?** No. The names describe
the data source only.

**Something is wrong.** Open an issue on the **Issues** tab with the run ID. Issues are
answered within 14 days.

This Actor is provided as is, under Apify's Standard Actor Contract. It is not affiliated with
or endorsed by Workable, Recruitee (Tellent), Personio, Teamtailor or SmartRecruiters (SAP).

# Actor input Schema

## `boards` (type: `array`):

One per line, up to 200 per run. Accepted: a careers URL (apply.workable.com/acme, acme.recruitee.com, acme.jobs.personio.de, acme.teamtailor.com, jobs.smartrecruiters.com/Acme), a name with its ATS (workable:acme, recruitee:acme, personio:acme, teamtailor:acme, smartrecruiters:Acme), or a bare name (acme), tried on Workable, Recruitee, Personio, then Teamtailor (not SmartRecruiters). Aggregators, BambooHR, Workday and other ATSs are refused.

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

Add description_text (the posting as plain text) to every row. Off by default: rows stay small. The price per job is the same either way.

## `maxJobsPerBoard` (type: `integer`):

Stop reading a career site after this many jobs. From 1 to 10000; default 1000. Each job is one charged event, so this also caps the cost per site.

## Actor input object example

```json
{
  "boards": [
    "https://personio-gmbh.jobs.personio.de",
    "https://career.teamtailor.com",
    "https://kaizo.recruitee.com",
    "https://apply.workable.com/blueground",
    "https://jobs.smartrecruiters.com/BoschGroup"
  ],
  "includeDescription": false,
  "maxJobsPerBoard": 5
}
```

# Actor output Schema

## `jobs` (type: `string`):

One row per open job (title, company, location, remote, department, posted_at, apply_url, (SmartRecruiters: employment_type, experience_level), ats, board, job_id, dedup_key; description_text when includeDescription is on), plus one row with error, message, tried and charged per career site that could not be read.

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

Per career site: the ATS that answered, the outcome and the number of jobs found and delivered.

# 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 = {
    "boards": [
        "https://personio-gmbh.jobs.personio.de",
        "https://career.teamtailor.com",
        "https://kaizo.recruitee.com",
        "https://apply.workable.com/blueground",
        "https://jobs.smartrecruiters.com/BoschGroup"
    ],
    "maxJobsPerBoard": 5
};

// Run the Actor and wait for it to finish
const run = await client.actor("atarivo/ats-jobs-api-workable-recruitee-personio-teamtailor").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 = {
    "boards": [
        "https://personio-gmbh.jobs.personio.de",
        "https://career.teamtailor.com",
        "https://kaizo.recruitee.com",
        "https://apply.workable.com/blueground",
        "https://jobs.smartrecruiters.com/BoschGroup",
    ],
    "maxJobsPerBoard": 5,
}

# Run the Actor and wait for it to finish
run = client.actor("atarivo/ats-jobs-api-workable-recruitee-personio-teamtailor").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 '{
  "boards": [
    "https://personio-gmbh.jobs.personio.de",
    "https://career.teamtailor.com",
    "https://kaizo.recruitee.com",
    "https://apply.workable.com/blueground",
    "https://jobs.smartrecruiters.com/BoschGroup"
  ],
  "maxJobsPerBoard": 5
}' |
apify call atarivo/ats-jobs-api-workable-recruitee-personio-teamtailor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,atarivo/ats-jobs-api-workable-recruitee-personio-teamtailor"
        }
    }
}
```

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/galfxlvGDoKkBLdzX/builds/rkX6LkkE4fsdJjv82/openapi.json
