# Handshake Public Jobs Scraper (`automation-lab/handshake-public-jobs-scraper`) Actor

Extract anonymous public Handshake jobs with employers, role groups, remote locations, salaries, dates, and full descriptions. No login required. Export to JSON, CSV, Excel, or API and schedule recurring hiring-market refreshes.

- **URL**: https://apify.com/automation-lab/handshake-public-jobs-scraper.md
- **Developed by:** [Stas Persiianenko](https://apify.com/automation-lab) (community)
- **Categories:** Jobs
- **Stats:** 7 total users, 5 monthly users, 65.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

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

## What's an Apify Actor?

Actors are a software tools running on the Apify platform, for all kinds of web data extraction and automation use cases.
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.

In JavaScript/TypeScript projects, use official [JavaScript/TypeScript client](https://docs.apify.com/api/client/js/docs.md):

```bash
npm install apify-client
```

In Python projects, use official [Python client library](https://docs.apify.com/api/client/python/docs.md):

```bash
pip install apify-client
```

In shell scripts, use [Apify CLI](https://docs.apify.com/cli/docs.md):

````bash
# MacOS / Linux
curl -fsSL https://apify.com/install-cli.sh | bash
# Windows
irm https://apify.com/install-cli.ps1 | iex
```bash

In AI frameworks, you might use the [Apify MCP server](https://docs.apify.com/integrations/mcp.md).

If your project is in a different language, use 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

## Handshake Public Jobs Scraper

Extract **public Handshake jobs** without a login or API key. Handshake Public Jobs Scraper turns anonymous Handshake search pages and public job URLs into structured recruiting data with employers, locations, remote status, compensation, dates, and full descriptions.

Use a public `joinhandshake.com/find-jobs/` URL to discover openings, or submit specific `app.joinhandshake.com/public/jobs/` URLs for targeted lookups. The Actor uses lightweight HTTP requests instead of a browser, deduplicates records by Handshake job ID, and exports results to JSON, CSV, Excel, XML, or RSS through Apify.

### What does Handshake Public Jobs Scraper do?

Handshake Public Jobs Scraper combines two anonymous Handshake data surfaces:

- 🔎 Public SEO job-search pages for discovery and search-level fields
- 📄 Public job detail pages for complete Schema.org job-posting data
- 💼 Employer names, websites, and logo URLs
- 📍 City, state, and normalized remote status
- 💰 Salary ranges, currency, unit, and pay schedule
- 🗓️ First-active, posted, expiry, and scrape dates
- 📝 Full description HTML plus clean plain text
- 🔗 Canonical job and source-search URLs

No Handshake student, employer, or institution account is required. This Actor intentionally covers public jobs only; it does not claim personalized campus results or authenticated application data.

### Who is it for?

**Job-board operators**

- Refresh public early-career and graduate job inventories.
- Import normalized records into a niche board or search index.
- Detect newly posted and expired roles with scheduled runs.

**Recruiters and sourcing teams**

- Build target lists from public Handshake searches.
- Compare employers, role groups, locations, and compensation.
- Route matching openings into a CRM, spreadsheet, or alert channel.

**Campus and early-career teams**

- Monitor the anonymous job market visible outside institution accounts.
- Track remote software and other public role categories.
- Research employer hiring activity without manual copying.

**Labor-market analysts and data teams**

- Collect repeatable snapshots for salary and hiring trend analysis.
- Preserve posting and validity dates for time-series workflows.
- Feed clean job descriptions into classification or language models.

### Why use this Handshake jobs scraper?

- ✅ **No login or API key:** works with the public pages users can open anonymously.
- ⚡ **HTTP-first:** avoids browser overhead and keeps runs fast and economical.
- 🧩 **Merged records:** combines search hydration and job-detail JSON-LD.
- 🔁 **Deduplicated:** the same job from multiple sources is saved once.
- 🛡️ **Resilient:** retries temporary errors and skips one malformed job without losing the run.
- 🎯 **Flexible:** use search URLs, direct job URLs, or both.
- 📦 **Export-ready:** download JSON, CSV, Excel, XML, or RSS.
- ⏰ **Automatable:** schedule runs, trigger webhooks, or call through the API.

### What Handshake job data can you extract?

| Category | Fields |
|---|---|
| Identity | `jobId`, `jobUrl`, `title` |
| Employer | `employerName`, `employerWebsite`, `employerLogoUrl` |
| Classification | `jobType`, `roleGroups`, `employmentType` |
| Location | `city`, `state`, `isRemote` |
| Compensation | `salaryMin`, `salaryMax`, `salaryCurrency`, `salaryUnit`, `paySchedule` |
| Dates | `firstActiveAt`, `datePosted`, `validThrough`, `scrapedAt` |
| Content | `descriptionHtml`, `descriptionText` |
| Provenance | `sourceSearchUrl` |

Fields remain `null` when a public posting does not publish that information. Salary numbers should always be interpreted together with `paySchedule` and `salaryUnit`; an hourly wage is not an annual salary.

### How much does it cost to scrape Handshake jobs?

This Actor uses **pay-per-event pricing**. A small start charge covers initialization and search discovery, then each saved job is charged as one `job` event. Platform compute is included.

| Event | Free | Starter | Scale | Business |
|---|---:|---:|---:|---:|
| Run start | $0.005 | $0.005 | $0.005 | $0.005 |
| Job extracted | $0.00010859 | $0.000094429 | $0.000073655 | $0.000056658 |

At these formula-validated prices, 100 jobs cost about **$0.016 on Free** or **$0.014 on Starter**, including the start event. A 1,000-job run costs about **$0.114 on Free** or **$0.099 on Starter**.

Apify Free users receive monthly platform credits, so a small evaluation can normally be completed from those credits. Higher plans receive additional volume discounts.

### How to scrape public Handshake jobs

1. Open Handshake Public Jobs Scraper on Apify.
2. Paste one or more public `joinhandshake.com/find-jobs/` URLs into **Public job search URLs**.
3. Optionally add specific `app.joinhandshake.com/public/jobs/{id}` links.
4. Set **Maximum jobs** to control output and cost.
5. Choose whether to include descriptions or apply remote/salary filters.
6. Click **Start**.
7. Preview the dataset, then export it or connect an integration.

A small search example:

```json
{
  "searchUrls": [
    { "url": "https://joinhandshake.com/find-jobs/remote/software-engineering/" }
  ],
  "maxItems": 10,
  "includeDescriptions": true
}
````

A direct lookup example:

```json
{
  "jobUrls": [
    { "url": "https://app.joinhandshake.com/public/jobs/11196104" }
  ],
  "maxItems": 1
}
```

A filtered monitoring example:

```json
{
  "searchUrls": [
    { "url": "https://joinhandshake.com/find-jobs/remote/software-engineering/" }
  ],
  "maxItems": 100,
  "remoteOnly": true,
  "minSalary": 50
}
```

### Input parameters

| Parameter | Type | Default | Description |
|---|---|---:|---|
| `searchUrls` | array | prefilled public search | Public Handshake SEO search URLs used to discover job IDs. |
| `jobUrls` | array | `[]` | Direct Handshake public job URLs for targeted lookups. |
| `maxItems` | integer | `10` | Maximum matching jobs saved across all sources. |
| `includeDescriptions` | boolean | `true` | Save complete description HTML and normalized text. |
| `remoteOnly` | boolean | `false` | Save only jobs identified as remote. |
| `minSalary` | number | unset | Require the published salary maximum to reach this threshold. |

At least one search URL or job URL is required. URLs are validated to prevent accidental crawling of unrelated pages.

### Output example

Each dataset row represents one public Handshake job:

```json
{
  "jobId": "11196104",
  "jobUrl": "https://app.joinhandshake.com/public/jobs/11196104",
  "title": "Applied AI Engineer",
  "employerName": "Squoosh.AI",
  "employerWebsite": "https://squoosh.ai/",
  "employerLogoUrl": "https://handshake-production-cdn.joinhandshake.com/static_assets/default_institution_1.png",
  "jobType": "Job",
  "roleGroups": ["Software Developers and Engineers"],
  "city": null,
  "state": null,
  "isRemote": true,
  "salaryMin": 90000,
  "salaryMax": 180000,
  "salaryCurrency": "USD",
  "salaryUnit": "YEAR",
  "paySchedule": "Annually",
  "firstActiveAt": "2026-07-10T02:01:30.000Z",
  "datePosted": "2026-07-10T02:01:30Z",
  "validThrough": "2026-08-09T03:59:59Z",
  "employmentType": ["FULL_TIME"],
  "descriptionHtml": "<p>Applied AI Engineer...</p>",
  "descriptionText": "Applied AI Engineer...",
  "sourceSearchUrl": "https://joinhandshake.com/find-jobs/remote/software-engineering/",
  "scrapedAt": "2026-07-15T00:00:00.000Z"
}
```

When a direct job URL is supplied, `sourceSearchUrl` is `null`. When descriptions are disabled, both description fields are `null`.

### Tips for best results

- 🧪 Start with 5–10 jobs to validate your search and preferred fields.
- 🔗 Use the exact public URL copied from Handshake rather than constructing a category slug.
- 💰 Read `salaryMin` and `salaryMax` together with `paySchedule` and `salaryUnit`.
- 📄 Keep descriptions enabled for semantic search, classification, or summarization.
- ⚡ Disable descriptions when you only need search summaries and want smaller exports.
- 🔁 Schedule searches daily or weekly, then deduplicate downstream by `jobId`.
- ⏳ Treat `validThrough` as the source's advertised expiry, not a guarantee that an employer has not closed a role early.

### Scheduling and monitoring jobs

Run the Actor on an Apify schedule to create regular snapshots. A practical workflow is:

1. Save one task per public Handshake search URL.
2. Run each task once per day.
3. Export or webhook the dataset to your database.
4. Upsert records by `jobId`.
5. Alert when a previously unseen ID appears or when `validThrough` approaches.

The Actor does not maintain hidden cross-run state. This makes each result reproducible and lets your own datastore define new, changed, or expired records.

### Integrations

**Handshake jobs → Google Sheets**

Append public openings to a recruiting tracker, then filter by employer, remote status, pay, or expiry date.

**Handshake jobs → Slack or Discord**

Schedule a targeted search and send newly observed job IDs to a campus recruiting or sourcing channel.

**Handshake jobs → Make or Zapier**

Trigger a workflow that enriches employers, creates CRM records, or assigns jobs to recruiters based on role groups.

**Handshake jobs → database or data warehouse**

Use webhooks or the API to upsert jobs by `jobId` and build time-series hiring dashboards.

**Handshake jobs → AI pipeline**

Send `descriptionText` to a classifier for skill extraction, seniority labeling, taxonomy mapping, or candidate matching.

### API usage

**Node.js**

Install the client:

```bash
npm install apify-client
```

Run the Actor and retrieve its dataset:

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('automation-lab/handshake-public-jobs-scraper').call({
  searchUrls: [{ url: 'https://joinhandshake.com/find-jobs/remote/software-engineering/' }],
  maxItems: 25,
  includeDescriptions: true,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
```

### Using the Apify API with Python

```bash
pip install apify-client
```

```python
import os
from apify_client import ApifyClient

client = ApifyClient(os.environ['APIFY_TOKEN'])
run = client.actor('automation-lab/handshake-public-jobs-scraper').call(run_input={
    'jobUrls': [{'url': 'https://app.joinhandshake.com/public/jobs/11196104'}],
    'maxItems': 1,
})
items = client.dataset(run['defaultDatasetId']).list_items().items
print(items)
```

### Using the Apify API with cURL

```bash
curl -X POST \
  "https://api.apify.com/v2/acts/automation-lab~handshake-public-jobs-scraper/runs?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "searchUrls": [{"url":"https://joinhandshake.com/find-jobs/remote/software-engineering/"}],
    "maxItems": 10,
    "remoteOnly": true
  }'
```

Use the returned `defaultDatasetId` with the dataset items endpoint to download JSON, CSV, Excel, XML, or RSS.

### Use Handshake Public Jobs Scraper with AI agents via MCP

This Actor can be used by AI assistants that support the [Model Context Protocol](https://docs.apify.com/platform/integrations/mcp).

For Claude Code:

```bash
claude mcp add --transport http apify "https://mcp.apify.com?tools=automation-lab/handshake-public-jobs-scraper"
```

For Claude Desktop, Cursor, or VS Code, add this MCP configuration:

```json
{
  "mcpServers": {
    "apify": {
      "url": "https://mcp.apify.com?tools=automation-lab/handshake-public-jobs-scraper"
    }
  }
}
```

Example prompts:

- “Use `automation-lab/handshake-public-jobs-scraper` to find 25 public remote software engineering jobs and summarize the salary ranges.”
- “Extract this Handshake public job URL and tell me its employer, expiry date, employment type, and required skills.”
- “Run my Handshake search, return only remote jobs above the published pay threshold, and prepare a CSV-ready table.”

OAuth connects the MCP server to your Apify account on first use.

### Is it legal to scrape public Handshake jobs?

Scraping publicly available business and job-posting data is generally lawful in many jurisdictions, but legality depends on your location, purpose, and handling of the data. This Actor accesses anonymous public pages only; it does not bypass login controls or collect private student data.

You are responsible for complying with Handshake's terms, applicable laws, robots guidance, privacy obligations, and employer content rights. Avoid using job data for discrimination, unsolicited spam, or decisions that require consent. Collect only what you need, retain it for an appropriate period, and honor deletion or correction obligations that apply to your workflow.

### Limitations

- Only anonymous public Handshake search and job-detail pages are supported.
- Institution-personalized results, applications, and student or recruiter profiles are out of scope.
- A search page may expose IDs for jobs that expire between discovery and detail retrieval; those jobs are logged and skipped.
- Handshake decides which salary, location, and role fields are published, so optional values can be `null`.
- The minimum-salary filter compares the numeric published maximum; always consider the pay schedule.
- Public page structure can change. The Actor reports parsing failures rather than inventing values.
- `maxItems` limits saved matching records, not the number of candidate URLs inspected when filters exclude jobs.

### Troubleshooting

**Why did my run return no jobs?**

Confirm the URL opens anonymously and begins with `https://joinhandshake.com/find-jobs/` or `https://app.joinhandshake.com/public/jobs/`. Remove filters temporarily, especially a high `minSalary`, and inspect the run log for expired jobs.

**Why are salary or location fields empty?**

The employer may not publish those fields. Null values reflect the source rather than an extraction error. Check the description for unstructured compensation text if your workflow permits text analysis.

**Why did fewer jobs appear than Handshake's displayed total?**

A job may expire after the search page is generated, or your remote/salary filters may exclude it. The Actor logs malformed or unavailable public detail pages and continues with other candidates.

**How fast is it?**

The Actor uses direct HTTP. Runtime mainly depends on the number of detail pages requested and their response latency. A five-job prefilled run should normally complete well under two minutes.

### FAQ

**Does this Actor require a Handshake account?**

No. It supports only the public search and public job-detail pages that load without authentication.

**Can I scrape a specific job instead of a search?**

Yes. Add its `app.joinhandshake.com/public/jobs/{id}` URL to `jobUrls`. You can combine direct and search URLs in one run.

**How are duplicate jobs handled?**

Jobs are keyed by their numeric Handshake job ID. If the same ID occurs in several searches or direct inputs, it is saved once.

**Can I monitor new jobs?**

Yes. Schedule recurring runs and upsert downstream by `jobId`. Compare each snapshot with your previous one to identify newly observed jobs.

**Is this the official Handshake API?**

No. This independent Actor extracts structured data from anonymous public web pages. It is not affiliated with or endorsed by Handshake.

**What counts toward `maxItems`?**

Only complete records that pass your filters and are saved to the dataset count toward the limit.

### Other recruiting and jobs scrapers

Combine this Actor with other Automation Labs tools:

- [LinkedIn Jobs Scraper](https://apify.com/automation-lab/linkedin-jobs-scraper) — broader professional job discovery.
- [Indeed Scraper](https://apify.com/automation-lab/indeed-scraper) — search large public job inventories.
- [Personio Jobs Scraper](https://apify.com/automation-lab/personio-jobs-scraper) — extract jobs from public Personio career sites.
- [Greenhouse Jobs Scraper](https://apify.com/automation-lab/greenhouse-jobs-scraper) — collect public ATS job postings.
- [Lever Jobs Scraper](https://apify.com/automation-lab/lever-jobs-scraper) — extract public Lever openings.

Use only related tools that fit your data source and verify each target's public availability before building a multi-source workflow.

# Actor input Schema

## `searchUrls` (type: `array`):

Add joinhandshake.com/find-jobs URLs. The Actor discovers all job IDs exposed by each public SEO page.

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

Add app.joinhandshake.com/public/jobs/{id} URLs for direct job lookups.

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

Stop after saving this many matching job records across all sources.

## `includeDescriptions` (type: `boolean`):

Fetch and save description HTML and clean text from each public job detail page.

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

Save only jobs marked as remote on their public detail or search record.

## `minSalary` (type: `number`):

Optionally require the published salary maximum to meet this numeric threshold. Units follow each job's pay schedule.

## Actor input object example

```json
{
  "searchUrls": [
    {
      "url": "https://joinhandshake.com/find-jobs/remote/software-engineering/"
    }
  ],
  "jobUrls": [],
  "maxItems": 5,
  "includeDescriptions": true,
  "remoteOnly": false
}
```

# Actor output Schema

## `overview` (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 = {
    "searchUrls": [
        {
            "url": "https://joinhandshake.com/find-jobs/remote/software-engineering/"
        }
    ],
    "jobUrls": [],
    "maxItems": 5,
    "includeDescriptions": true,
    "remoteOnly": false,
    "minSalary": 0
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation-lab/handshake-public-jobs-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 = {
    "searchUrls": [{ "url": "https://joinhandshake.com/find-jobs/remote/software-engineering/" }],
    "jobUrls": [],
    "maxItems": 5,
    "includeDescriptions": True,
    "remoteOnly": False,
    "minSalary": 0,
}

# Run the Actor and wait for it to finish
run = client.actor("automation-lab/handshake-public-jobs-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 '{
  "searchUrls": [
    {
      "url": "https://joinhandshake.com/find-jobs/remote/software-engineering/"
    }
  ],
  "jobUrls": [],
  "maxItems": 5,
  "includeDescriptions": true,
  "remoteOnly": false,
  "minSalary": 0
}' |
apify call automation-lab/handshake-public-jobs-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=automation-lab/handshake-public-jobs-scraper",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

```json
{
    "openapi": "3.0.1",
    "info": {
        "title": "Handshake Public Jobs Scraper",
        "description": "Extract anonymous public Handshake jobs with employers, role groups, remote locations, salaries, dates, and full descriptions. No login required. Export to JSON, CSV, Excel, or API and schedule recurring hiring-market refreshes.",
        "version": "0.1",
        "x-build-id": "C4ikmtFeyi80SNUZH"
    },
    "servers": [
        {
            "url": "https://api.apify.com/v2"
        }
    ],
    "paths": {
        "/acts/automation-lab~handshake-public-jobs-scraper/run-sync-get-dataset-items": {
            "post": {
                "operationId": "run-sync-get-dataset-items-automation-lab-handshake-public-jobs-scraper",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        },
        "/acts/automation-lab~handshake-public-jobs-scraper/runs": {
            "post": {
                "operationId": "runs-sync-automation-lab-handshake-public-jobs-scraper",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor and returns information about the initiated run in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/runsResponseSchema"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/acts/automation-lab~handshake-public-jobs-scraper/run-sync": {
            "post": {
                "operationId": "run-sync-automation-lab-handshake-public-jobs-scraper",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "inputSchema": {
                "type": "object",
                "properties": {
                    "searchUrls": {
                        "title": "🔎 Public job search URLs",
                        "type": "array",
                        "description": "Add joinhandshake.com/find-jobs URLs. The Actor discovers all job IDs exposed by each public SEO page.",
                        "items": {
                            "type": "object",
                            "required": [
                                "url"
                            ],
                            "properties": {
                                "url": {
                                    "type": "string",
                                    "title": "URL of a web page",
                                    "format": "uri"
                                }
                            }
                        }
                    },
                    "jobUrls": {
                        "title": "📄 Public job URLs",
                        "type": "array",
                        "description": "Add app.joinhandshake.com/public/jobs/{id} URLs for direct job lookups.",
                        "items": {
                            "type": "object",
                            "required": [
                                "url"
                            ],
                            "properties": {
                                "url": {
                                    "type": "string",
                                    "title": "URL of a web page",
                                    "format": "uri"
                                }
                            }
                        }
                    },
                    "maxItems": {
                        "title": "Maximum jobs",
                        "minimum": 1,
                        "maximum": 10000,
                        "type": "integer",
                        "description": "Stop after saving this many matching job records across all sources.",
                        "default": 10
                    },
                    "includeDescriptions": {
                        "title": "Include full descriptions",
                        "type": "boolean",
                        "description": "Fetch and save description HTML and clean text from each public job detail page.",
                        "default": true
                    },
                    "remoteOnly": {
                        "title": "Remote jobs only",
                        "type": "boolean",
                        "description": "Save only jobs marked as remote on their public detail or search record.",
                        "default": false
                    },
                    "minSalary": {
                        "title": "Minimum salary",
                        "minimum": 0,
                        "type": "number",
                        "description": "Optionally require the published salary maximum to meet this numeric threshold. Units follow each job's pay schedule."
                    }
                }
            },
            "runsResponseSchema": {
                "type": "object",
                "properties": {
                    "data": {
                        "type": "object",
                        "properties": {
                            "id": {
                                "type": "string"
                            },
                            "actId": {
                                "type": "string"
                            },
                            "userId": {
                                "type": "string"
                            },
                            "startedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "finishedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "status": {
                                "type": "string",
                                "example": "READY"
                            },
                            "meta": {
                                "type": "object",
                                "properties": {
                                    "origin": {
                                        "type": "string",
                                        "example": "API"
                                    },
                                    "userAgent": {
                                        "type": "string"
                                    }
                                }
                            },
                            "stats": {
                                "type": "object",
                                "properties": {
                                    "inputBodyLen": {
                                        "type": "integer",
                                        "example": 2000
                                    },
                                    "rebootCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "restartCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "resurrectCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "computeUnits": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "options": {
                                "type": "object",
                                "properties": {
                                    "build": {
                                        "type": "string",
                                        "example": "latest"
                                    },
                                    "timeoutSecs": {
                                        "type": "integer",
                                        "example": 300
                                    },
                                    "memoryMbytes": {
                                        "type": "integer",
                                        "example": 1024
                                    },
                                    "diskMbytes": {
                                        "type": "integer",
                                        "example": 2048
                                    }
                                }
                            },
                            "buildId": {
                                "type": "string"
                            },
                            "defaultKeyValueStoreId": {
                                "type": "string"
                            },
                            "defaultDatasetId": {
                                "type": "string"
                            },
                            "defaultRequestQueueId": {
                                "type": "string"
                            },
                            "buildNumber": {
                                "type": "string",
                                "example": "1.0.0"
                            },
                            "containerUrl": {
                                "type": "string"
                            },
                            "usage": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "integer",
                                        "example": 1
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "usageTotalUsd": {
                                "type": "number",
                                "example": 0.00005
                            },
                            "usageUsd": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "number",
                                        "example": 0.00005
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
```
