# Job Postings API: Greenhouse, Lever, Ashby, SmartRecruiters (`solidfetch/ats-jobs-feed`) Actor

One feed of open jobs from company career pages on Greenhouse, Lever, Ashby and SmartRecruiters. Give company names or careers-page URLs, filter by keyword, location, remote, department and date, and get only new jobs each run. Salary where shown. Free hiring-signal row per company. Pay per job.

- **URL**: https://apify.com/solidfetch/ats-jobs-feed.md
- **Developed by:** [Solid Fetch](https://apify.com/solidfetch) (community)
- **Categories:** Jobs, Lead generation
- **Stats:** 2 total users, 1 monthly users, 33.3% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.20 / 1,000 jobs

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

## Job Postings API: Greenhouse, Lever, Ashby and SmartRecruiters Jobs Feed

Get the open jobs of any company that publishes its jobs on Greenhouse, Lever, Ashby or SmartRecruiters, in one schema, as an API or a scraper alternative. Give a list of company names or careers-page URLs and the actor works out the vendor from each address. Filter by keyword, location, remote, department and posting date, or switch on the monitor to get only the jobs that are new since your last run. Each job is one row with the title, department, team, locations, remote and workplace type, employment type, salary when the vendor shows it, posting date, job and apply URLs and the description. Every company also gets a free hiring-signal row: open roles, roles by department, remote roles, postings in the last 7 days, and what is new or closed since the last run.

The actor reads only the public job-board APIs that the four vendors offer for publishing jobs. There is no login, no browser, no proxy and no scraping of company websites or of LinkedIn.

### What people use it for

- **Hiring signals for sales and recruiting.** A company that opens ten engineering roles in a week is growing a team. Run the monitor daily over a list of target accounts and get the new roles, plus a free row per company with its open roles by department.
- **Job boards and alerts.** Feed a niche job board or a Slack channel with fresh remote roles from the companies you care about. Each run returns only what is new, so the feed has no duplicates.
- **Market and salary research.** Collect open roles with their salary ranges (Ashby, Lever and Greenhouse show them when the company turns pay transparency on) across many companies in one table, without learning four different APIs.
- **Competitor watch.** Follow which teams a competitor is hiring for and where.

### How to use it

Read three companies on three vendors. The vendor follows from the address, or from the prefix of a board id:

```json
{
    "companies": [
        "https://boards.greenhouse.io/airbnb",
        "https://jobs.lever.co/zoox",
        "ashby:ramp",
        "https://careers.smartrecruiters.com/Canva"
    ],
    "maxItems": 500
}
```

Remote engineering jobs posted in the last two weeks, in Europe or the US:

```json
{
    "companies": ["greenhouse:stripe", "lever:spotify", "ashby:notion"],
    "keywords": ["engineer", "developer"],
    "excludeKeywords": ["intern"],
    "remoteOnly": true,
    "locations": ["Europe", "United States", "Poland", "Germany"],
    "postedSince": "2 weeks",
    "includeDescription": "none"
}
```

The monitor, scheduled daily. The first run records what each company has open and returns no jobs. From the next run on, you get only the new ones:

```json
{
    "companies": ["greenhouse:airbnb", "lever:zoox", "ashby:ramp"],
    "departments": ["Engineering", "Product"],
    "onlyNew": true
}
```

A field name the actor does not know stops the run with a hint such as `"keyword" (did you mean "keywords"?)`, so a typo cannot turn into a run of unfiltered jobs that you pay for.

### Input

| Field | What it does |
|---|---|
| `companies` | The companies to read, up to 2,000. Each entry is a careers-page address (`boards.greenhouse.io/stripe`, `job-boards.greenhouse.io/stripe`, `jobs.lever.co/spotify`, `jobs.eu.lever.co/acme`, `jobs.ashbyhq.com/ramp`, `careers.smartrecruiters.com/Canva`, an address with a job id in it works too), a board id with its vendor (`greenhouse:stripe`, `lever:spotify`, `ashby:ramp`, `smartrecruiters:Canva`, short forms `gh` and `sr` work), or a bare id such as `stripe`. A start-URL object with a `url` is also accepted. |
| `ats` | Which vendor to read for bare ids. `auto` (default) reads Greenhouse, Lever, Ashby and SmartRecruiters and takes the one vendor with open jobs. When more than one vendor has open jobs under the id, nothing is saved or charged for it and it gets a free `ambiguous-company` error row that names the vendors. Name the vendor if you know it, to save requests and to avoid a same-named board on another vendor. |
| `keywords`, `excludeKeywords` | Keep jobs whose title has any of the keywords, and drop jobs that have any of the excluded ones. Matching ignores case and accents and starts at a word: `engineer` finds "Engineering Manager", `go` does not find "Django". |
| `keywordScope` | `title` (default) or `titleAndDescription`. |
| `locations` | Keep jobs that have any of these in one of their locations or in the country. Locations are free text as the company wrote them ("Remote (US)", "NYC"), so list the variants you need. |
| `departments` | Keep jobs whose department or team contains any of these. |
| `remoteOnly` | Keep jobs marked remote, or with a remote location. |
| `postedSince` | A date (`2026-10-15`) or a period back from today (`7 days`, `2 weeks`, `1 month`). Jobs with no posting date are dropped. |
| `onlyNew` | Monitor mode. See "How the monitor works". |
| `emitOnFirstRun` | With `onlyNew`, also return every job of a company the first time the actor sees it. |
| `includeDescription` | `text` (default), `html` or `none`. |
| `maxItems` | Stop after this many jobs in the run, up to 1,000,000. Default 1,000. |
| `maxJobsPerCompany` | Save at most this many jobs, the newest, from each company. |
| `stateStoreName` | Default `ats-jobs-feed-state`. The key-value store on your account that holds the monitor's memory. Use another name to keep a separate watch, for example per client. |

### Output

The dataset has three kinds of rows, told apart by `rowType`: `job` (charged), `summary` (one per company, free) and `error` (one per entry that gave no jobs, free). The dataset views "Jobs", "Hiring signals per company" and "Entries that gave no jobs" show each kind as a table. To keep only jobs in an export, filter on `rowType` being `job`.

A job row, built from a recorded Ashby posting:

```json
{
    "rowType": "job",
    "id": "ashby:ramp:34413f8d-26bf-4bbc-8ade-eb309a0e2245",
    "ats": "ashby",
    "companyId": "ramp",
    "company": "ramp",
    "jobId": "34413f8d-26bf-4bbc-8ade-eb309a0e2245",
    "title": "Security Engineer, Cloud",
    "department": "Engineering",
    "team": "Backend",
    "employmentType": "full-time",
    "location": "New York, NY (HQ)",
    "locations": ["New York, NY (HQ)", "Remote (Canada)", "Remote (US)", "Miami, FL"],
    "country": "USA",
    "remote": true,
    "workplaceType": "hybrid",
    "salaryMin": 211400,
    "salaryMax": 290600,
    "salaryCurrency": "USD",
    "salaryPeriod": "year",
    "salaryText": "$211.4K - $290.6K",
    "postedAt": "2026-04-07T17:12:35.753Z",
    "updatedAt": null,
    "url": "https://jobs.ashbyhq.com/ramp/34413f8d-26bf-4bbc-8ade-eb309a0e2245",
    "applyUrl": "https://jobs.ashbyhq.com/ramp/34413f8d-26bf-4bbc-8ade-eb309a0e2245/application",
    "language": null,
    "description": "About Ramp\nRamp is building the smart infrastructure for finance teams...",
    "descriptionError": null,
    "source": "Ashby public job-board API",
    "fetchedAt": "2026-10-22T09:00:00.000Z"
}
```

| Field | Meaning |
|---|---|
| `id`, `jobId` | `id` is `vendor:company:jobId` and is unique across vendors. `jobId` is the vendor's own id. |
| `ats`, `companyId`, `company` | The vendor (`greenhouse`, `lever`, `ashby`, `smartrecruiters`), the board id you gave, and the company name. Greenhouse and SmartRecruiters send the company name, Lever and Ashby do not, so for those `company` is the board id. |
| `title`, `department`, `team` | As the company wrote them. SmartRecruiters has no team, and its department falls back to the job function (Sales, Engineering) when the company sets none. Greenhouse has no team. |
| `employmentType` | `full-time`, `part-time`, `contract`, `internship`, `temporary` or `other`, read from the vendor's label. `null` when the vendor sends none (Greenhouse sends it only on some boards). |
| `location`, `locations`, `country` | The main location, all locations of the job, and the country where the vendor gives one (Lever: two-letter code. Ashby: as written, "USA" or "United States". SmartRecruiters: two-letter code. Greenhouse: not given, `null`). |
| `remote`, `workplaceType` | `remote` is true when the vendor marks the job remote or lists a remote location. `workplaceType` is `remote`, `hybrid` or `onsite` where the vendor says it, `null` where it does not. Ashby marks a hybrid job remote when it also lists a remote location, so check `workplaceType` when the difference matters. |
| `salaryMin`, `salaryMax`, `salaryCurrency`, `salaryPeriod`, `salaryText` | The salary the vendor shows. Greenhouse: the pay range (no period, so `salaryPeriod` is `null`, ranges for several locations are merged when they share a currency). Lever: the salary range and its period. Ashby: the salary component and its period, with the vendor's short summary in `salaryText`. SmartRecruiters has no salary field. `null` when the company shows no pay. Bonus and equity are not included. |
| `postedAt`, `updatedAt` | When the job was first published (Greenhouse `first_published`, Lever `createdAt`, Ashby `publishedAt`, SmartRecruiters `releasedDate`) and, for Greenhouse only, when it was last updated. Lever and Ashby give no update time. |
| `url`, `applyUrl` | The job page and the apply page. |
| `description` | Plain text, HTML or `null`, as set by `includeDescription`. E-mail addresses are replaced with `[email removed]`. |
| `descriptionError` | `null` on a charged job. When a description was asked for and could not be fetched (SmartRecruiters needs one extra request per job), the job is saved free with `description` `null` and the reason here. |
| `language` | The language code where the vendor gives one (Greenhouse, SmartRecruiters). |
| `source`, `fetchedAt` | The vendor API the row comes from, and when it was read. |

A summary row, saved free for each company that was read:

```json
{
    "rowType": "summary",
    "ats": "ashby",
    "companyId": "ramp",
    "company": "ramp",
    "boardUrl": "https://jobs.ashbyhq.com/ramp",
    "openRoles": 4,
    "postedLast7Days": 0,
    "remoteRoles": 4,
    "newestPostedAt": "2026-08-12T20:22:32.685Z",
    "byDepartment": [
        { "department": "Engineering", "openRoles": 3 },
        { "department": "Risk", "openRoles": 1 }
    ],
    "matchedThisRun": 1,
    "savedThisRun": 1,
    "newSinceLastRun": null,
    "closedSinceLastRun": null,
    "baseline": false,
    "truncated": false,
    "fetchedAt": "2026-10-22T09:00:00.000Z"
}
```

`openRoles` counts every posting on the company's board, whatever your filters. `matchedThisRun` counts what passed the filters (in monitor mode, among the new postings only) and `savedThisRun` what was saved and charged. `newSinceLastRun` and `closedSinceLastRun` are filled in monitor mode only. `baseline` is true on the run that recorded the starting point. `truncated` is true when a SmartRecruiters board has more than 10,000 postings, which is the most the actor reads for one company.

An error row, saved free for each entry that gave no jobs:

```json
{
    "rowType": "error",
    "input": "ashby:nope",
    "ats": "ashby",
    "companyId": "nope",
    "company": null,
    "outcome": "not found",
    "error": "Ashby has no job board \"nope\"",
    "fetchedAt": "2026-10-22T09:00:00.000Z"
}
```

`outcome` is `invalid` (not a job-board address or id, for example a company's own careers site), `not found` (the vendor has no such board), `ambiguous-company` (a bare id with open jobs on more than one vendor: give `vendor:id` or the careers-page URL) or `failed` (the vendor did not answer after retries, or its format changed). A company with a board and no open roles gets a normal summary row with `openRoles` 0.

The run also saves a `SUMMARY` record in the key-value store: the companies read with their counts, the entries not found, failed or invalid, the bare ids found on more than one vendor, the companies not read because the run stopped, and how many descriptions could not be fetched.

### How the monitor works

With `onlyNew` on, the actor keeps a record of each company's open jobs in a named key-value store on your Apify account (`stateStoreName`). A run returns only the jobs that were not in the record, then updates the record.

- **The first run for a company records the starting point and returns no jobs.** The status message says so. This is on purpose: adding 200 companies must not return and charge for their whole job lists. Set `emitOnFirstRun` to get them anyway.
- Jobs that pass the filters are saved. A new job that fails a filter is still remembered, so it does not come back later as new.
- A job left out because of `maxJobsPerCompany`, `maxItems` or your cost limit stays unseen, and the next run returns it.
- A job whose description could not be fetched is saved free with `descriptionError` and stays unseen, so the next run tries again and charges it once it has its description.
- Each set of filters (`keywords`, `excludeKeywords`, `keywordScope`, `locations`, `departments`, `remoteOnly`) keeps its own record of a board, so two scheduled tasks with different filters on the same company do not hide jobs from each other. Order and case of the values do not matter, and `postedSince` is not part of it. Changing the filters starts a new record and a new starting point. A watch with no filters uses the same record as earlier versions of the actor.
- The summary row says how many postings were new and how many closed since the last run.
- Run it from a schedule (daily is usual). Two runs in a row over the same board return no jobs the second time.
- Jobs are matched by the vendor's job id. A job that the company deletes and posts again gets a new id and counts as new.
- If you change `stateStoreName`, the actor starts a new record and records a new starting point.

### Send results to Make, Zapier, n8n, Slack or a CRM

Start the actor or a saved task on a schedule. Trigger the next step on the finished run: in Make the Apify module "Watch Actor Runs", in Zapier the Apify trigger for a finished actor run, in n8n the Apify node. Read the rows with "Get Dataset Items" using the run's dataset ID. For your own endpoint, open the actor's Integrations tab and add an HTTP webhook on "Run succeeded". The payload contains `resource.defaultDatasetId`, which you can read at `https://api.apify.com/v2/datasets/<datasetId>/items?format=json`. Add `&view=jobs` for the job columns only, or `&format=csv` for a spreadsheet.

### Pricing

You pay per job saved (event `job`), and the price is on the Pricing tab. The summary rows and error rows are free and do not count against `maxItems`. So is a job saved without the description you asked for (`descriptionError` set), and a bare id found on more than one vendor is not saved or charged at all. A run that finds no new jobs costs only the start of the run. When a run reaches the maximum cost you set, it stops and keeps what was saved, and it never saves a job the remaining budget cannot pay for. If the platform migrates or restarts a run, the run picks up its own record of what it saved (`RUN_STATE` in the key-value store) and does not save or charge those jobs again.

### Source and legal

The actor calls only the public job-board APIs that the vendors publish for showing a company's own open jobs on its website:

- Greenhouse: `boards-api.greenhouse.io/v1/boards/{board}/jobs`
- Lever: `api.lever.co/v0/postings/{company}` (and the EU site `api.eu.lever.co` for a company not found on the global site)
- Ashby: `api.ashbyhq.com/posting-api/job-board/{board}`
- SmartRecruiters: `api.smartrecruiters.com/v1/companies/{company}/postings`

These need no login or key. The actor sends one request at a time with a pause between requests, retries politely, and honours `Retry-After`. It does not open company websites, career pages behind a login, or any page that is not one of these APIs, and it reads nothing from LinkedIn, Indeed or other job boards.

A company that runs its own careers site on top of one of these vendors usually still has a board on the vendor. Use the vendor address (for example `boards.greenhouse.io/stripe` for Stripe's jobs page) and not the company's own address, which the actor cannot read.

### Personal data

Job postings are company information. The actor does not output the names or e-mail addresses of recruiters or hiring managers, even where a vendor's API returns them (SmartRecruiters returns a posting creator on some postings, and the actor ignores it). Descriptions sometimes contain a recruiter's e-mail address, and every address is replaced with `[email removed]`. Names written inside a description text are not detected.

When a run fails, an error report with the error message and the shape of the input (which fields are set, how long the lists are), never your input values or your results, is sent to the developer to fix bugs.

### Limits

- A vendor's API shows what the company chose to publish. A job that is not on the public board is not in the feed. Ashby jobs marked unlisted are left out.
- Posting dates mean "first published" on all four vendors, and Lever and Ashby give no update time, so an edited job does not move.
- Salary appears only where the company turned it on. Greenhouse gives no pay period. SmartRecruiters gives no salary at all.
- A bare id looked up with `ats` set to `auto` takes the vendor that has open jobs under that name. If two vendors have jobs under it, nothing is saved for it, and if a vendor does not answer, the entry fails rather than guessing. One vendor with jobs can still be a different company of the same name, so name the vendor to be sure.
- Greenhouse boards on the EU site (`boards.eu.greenhouse.io`) are not read yet and get an `invalid` row.
- SmartRecruiters answers with an empty list both for a company it does not know and for one with no open jobs, so both give a `not found` row.
- SmartRecruiters descriptions need one extra request per saved job, so a run over a big SmartRecruiters board with descriptions is slower. Use `includeDescription: "none"` for counts and titles.
- A full run is as fast as the vendors answer. Greenhouse and Lever send a whole board in one response (several MB for the largest boards), and the actor waits a short time between requests.
- The actor stops with an error row for a company when its board comes back in a format the actor does not read, before saving anything from it, and the other companies continue.

If this actor saved you time, a short review on the actor page helps other people find it.

# Actor input Schema

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

The companies to read. Each entry is a careers-page address on Greenhouse, Lever, Ashby or SmartRecruiters (boards.greenhouse.io/stripe, jobs.lever.co/spotify, jobs.ashbyhq.com/ramp, careers.smartrecruiters.com/Canva), or a board id with its vendor (greenhouse:stripe, lever:spotify, ashby:ramp, smartrecruiters:Canva). A bare id such as stripe is looked up on each vendor in turn. Up to 2,000 per run. An entry that is not a job board, or not found, gets a free row.

## `ats` (type: `string`):

Which vendor to read for entries that are only a name, without an address or a vendor prefix. Entries with an address or a prefix already say it.

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

Keep jobs whose title has any of these words, without regard to case or accents. A keyword matches where it starts a word: engineer finds Engineering Manager, go does not find Django. Empty keeps every job.

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

Drop jobs whose title has any of these words, for example intern or staff.

## `keywordScope` (type: `string`):

Searching descriptions finds more but is noisier. For SmartRecruiters it costs one extra request per job that passes the other filters.

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

Keep jobs with any of these in one of their locations or in the country, for example Berlin, Poland, United States. Locations are free text as the company wrote them, so list the variants you care about (NYC, New York).

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

Keep jobs whose department or team contains any of these, for example Engineering, Sales.

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

Keep jobs the company marks as remote, or with a remote location. Hybrid jobs that also list a remote location count.

## `postedSince` (type: `string`):

Keep jobs posted on this date or later: a date (2026-10-15) or a period back from today (7 days, 2 weeks, 1 month). The posting date is the one the vendor shows (first published, or created). Jobs with no posting date are dropped.

## `onlyNew` (type: `boolean`):

Monitor mode. The actor remembers each company's open jobs in a named key-value store on your account and returns only the jobs it did not know at the end of the last run. The first run for a company records the starting point and returns no jobs (see the next option). Schedule the run daily or weekly.

## `emitOnFirstRun` (type: `boolean`):

With the monitor on, also return every job of a company the first time the actor sees it. Off by default so that adding 200 companies does not return and charge their whole job lists at once.

## `includeDescription` (type: `string`):

E-mail addresses in descriptions are replaced with \[email removed]. For SmartRecruiters the description costs one extra request per saved job.

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

Stop after this many jobs in the whole run. You pay per job saved, and the run stops before it would save a job your cost limit cannot pay for. Newest jobs of each company come first.

## `maxJobsPerCompany` (type: `integer`):

Save at most this many jobs (the newest) from each company. In monitor mode the jobs left out stay unseen and are returned by the next run.

## `stateStoreName` (type: `string`):

Name of the key-value store on your account where the monitor keeps what it has seen. Use a different name to keep a separate watch, for example per client.

## Actor input object example

```json
{
  "companies": [
    "https://boards.greenhouse.io/airbnb",
    "lever:zoox",
    "ashby:ramp"
  ],
  "ats": "auto",
  "keywordScope": "title",
  "remoteOnly": false,
  "onlyNew": false,
  "emitOnFirstRun": false,
  "includeDescription": "text",
  "maxItems": 25,
  "stateStoreName": "ats-jobs-feed-state"
}
```

# Actor output Schema

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

No description

## `companies` (type: `string`):

No description

## `errors` (type: `string`):

No description

## `summary` (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": [
        "https://boards.greenhouse.io/airbnb",
        "lever:zoox",
        "ashby:ramp"
    ],
    "maxItems": 25
};

// Run the Actor and wait for it to finish
const run = await client.actor("solidfetch/ats-jobs-feed").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": [
        "https://boards.greenhouse.io/airbnb",
        "lever:zoox",
        "ashby:ramp",
    ],
    "maxItems": 25,
}

# Run the Actor and wait for it to finish
run = client.actor("solidfetch/ats-jobs-feed").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": [
    "https://boards.greenhouse.io/airbnb",
    "lever:zoox",
    "ashby:ramp"
  ],
  "maxItems": 25
}' |
apify call solidfetch/ats-jobs-feed --silent --output-dataset

```

## MCP server setup

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

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/NEqfFeY9sjPkmjRc4/builds/ugBDoy6PscIjV7DiQ/openapi.json
