# Handshake Jobs Scraper — Public Job Listings (`khadinakbar/handshake-jobs-scraper`) Actor

Extract anonymous public Handshake job listings from public search or direct job URLs. Returns normalized titles, employers, locations, compensation, dates, descriptions, and canonical application URLs for early-career hiring research.

- **URL**: https://apify.com/khadinakbar/handshake-jobs-scraper.md
- **Developed by:** [Khadin Akbar](https://apify.com/khadinakbar) (community)
- **Categories:** Jobs, Lead generation, MCP servers
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 validated handshake job returneds

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

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

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

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

# README

## Handshake Jobs Scraper

Extract structured job data from Handshake's anonymous public job pages. This actor is built for recruiting teams, job-market researchers, and AI agents that need a clean export of public early-career and graduate opportunities—not applicant, student, or employer-account data.

It accepts public Handshake SEO search pages such as `https://joinhandshake.com/find-jobs/remote/software-engineering/`, discovers the listings shown there, follows each public job page, and returns one normalized record per unique job. You can also submit known `https://app.joinhandshake.com/public/jobs/{id}` URLs for direct lookup.

### What it extracts

Each successful dataset item is a validated public `JobPosting` record with a stable shape:

| Field | Description |
| --- | --- |
| `jobId`, `title`, `jobUrl` | Handshake's public job identity, title, and canonical public URL. |
| `companyName`, `companyUrl`, `companyLogoUrl` | Published employer identity and public links, when present. |
| `location`, `isRemote`, `employmentType`, `industry` | Published workplace, remote, contract, and industry signals. |
| `salaryMin`, `salaryMax`, `salaryCurrency`, `salaryUnit` | Published compensation; values are `null` when not publicly disclosed. |
| `postedAt`, `validThrough` | Publication and expiry timestamps when the listing exposes them. |
| `descriptionHtml`, `descriptionText` | The published description in HTML and plain text when descriptions are enabled. |
| `applyUrl`, `sourceSearchUrl`, `scrapedAt` | Application path, originating public search page, and extraction timestamp. |

The actor does not infer salary, invent location data, scrape account-only Handshake content, or return student/applicant information.

### When to use it

Use this actor when you need to:

- Refresh a public inventory of Handshake opportunities for recruiting or labor-market research.
- Monitor a specific public Handshake search category for newly visible jobs.
- Import public roles into an internal spreadsheet, CRM, dashboard, or semantic-search pipeline.
- Enrich a known public Handshake job URL with the visible description, employer, compensation, and dates.
- Filter supplied public results by title, company, remote status, or published compensation.

For account-specific results, personalized recommendations, applications, messages, student profiles, or private employer data, this is not the right tool. It deliberately supports only the anonymous public pages that can be loaded without a Handshake account.

### Inputs

`searchUrls` is for public SEO result pages. Start with the prefilled remote-software-engineering page or use another `joinhandshake.com/find-jobs/...` URL that you have opened publicly. The actor discovers all public job links on that page, then requests each detail page.

`jobUrls` is for direct public lookups. Supply one or more `app.joinhandshake.com/public/jobs/{id}` URLs when you already know the job IDs and do not need search discovery.

`maxItems` caps unique records across both input modes. Use it to keep collection volume and billable rows predictable. `includeDescriptions` defaults to `true`; disable it when you only need the summary fields. The remaining filters are local post-extraction filters: they narrow the rows found from the URLs you supplied and do not perform an unauthenticated global Handshake search.

#### Example: discover public remote engineering jobs

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

#### Example: look up known public jobs

```json
{
  "jobUrls": [
    "https://app.joinhandshake.com/public/jobs/11209339",
    "https://app.joinhandshake.com/public/jobs/11209137"
  ],
  "maxItems": 2,
  "includeDescriptions": true
}
```

### Output example

```json
{
  "jobId": "11209137",
  "title": "Software Engineer",
  "companyName": "Neurohire.ai",
  "location": "Remote",
  "isRemote": true,
  "employmentType": "FULL_TIME",
  "salaryMin": 80000,
  "salaryMax": 90000,
  "salaryCurrency": "USD",
  "salaryUnit": "YEAR",
  "postedAt": "2026-07-15T14:20:17.000Z",
  "jobUrl": "https://app.joinhandshake.com/public/jobs/11209137",
  "applyUrl": "https://app.joinhandshake.com/public/jobs/11209137",
  "sourceSearchUrl": "https://joinhandshake.com/find-jobs/remote/software-engineering/",
  "scrapedAt": "2026-07-19T00:00:00.000Z"
}
```

### API and agent use

Use this tool when an agent has a public Handshake search URL or public job URL and needs structured public job data. It returns one dataset item per persisted job and writes a compact `OUTPUT` record plus detailed `RUN_SUMMARY` diagnostics to the default key-value store.

The actor is designed for simple, narrow tool calls. Use a direct job URL for one job; use a public search URL for discovery. Do not use it to apply to jobs or to obtain private Handshake data.

### Pricing

Pricing is Pay per event + usage:

| Charge | Price |
| --- | --- |
| Validated public Handshake job written to the dataset | $0.003 per job |
| Apify compute and network usage | Charged separately by the platform |

Only records that pass the actor's validation and are successfully written to the default dataset are charged as `job-scraped`. A run with `maxItems: 10` has at most $0.03 in result-event charges, plus Apify platform usage. Invalid candidates, duplicate records, and failed pages are not billed as job records.

### Reliability and limits

The actor uses an HTTP-first path because Handshake's public SEO pages provide server-rendered job cards and individual public job pages expose Schema.org `JobPosting` data. It retries transient HTTP errors, deduplicates by public job ID, and preserves useful rows when some input pages fail.

Every terminal path writes `OUTPUT` and `RUN_SUMMARY` with one of: `COMPLETE`, `PARTIAL`, `VALID_EMPTY`, `INVALID_INPUT`, `UPSTREAM_FAILED`, or `CONFIG_ERROR`. A valid public URL that returns no matching rows is a successful `VALID_EMPTY` run; a blocked or unavailable public route with no useful output is reported honestly as `UPSTREAM_FAILED`.

Public pages can change, expire, or become unavailable. Use `sourceSearchUrl` and `scrapedAt` to retain collection provenance, and schedule modest, recurring runs rather than treating a listing as permanently available.

### Responsible use

Collect only public job-posting data and use it according to applicable law and the source platform's terms. Do not use this actor to make automated employment decisions or to collect applicant, student, or other private personal data.

# Actor input Schema

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

Use this when you want to discover jobs from Handshake's public SEO pages. Add https://joinhandshake.com/find-jobs/... URLs, for example https://joinhandshake.com/find-jobs/remote/software-engineering/.

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

Use this when you already know a Handshake public job. Add https://app.joinhandshake.com/public/jobs/{id} URLs; these are enriched from the public JobPosting page.

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

Use this when you need a predictable result cap. The actor saves at most this many unique validated job records across all input URLs; default 10, maximum 100.

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

Use this when you need the full public job description in HTML and plain text. Disable only for faster listing-only exports.

## `titleIncludes` (type: `string`):

Use this when the supplied public search contains several roles. Enter one case-insensitive title phrase, for example software engineer. This is a local filter, not a new Handshake search query.

## `companyIncludes` (type: `string`):

Use this when you need results for an employer from the supplied public URLs. Enter one case-insensitive phrase, for example Acme. This is not an employer-directory lookup.

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

Use this when you only want listings explicitly marked Remote or TELECOMMUTE on their public Handshake pages. Jobs with unknown workplace type are excluded.

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

Use this when you only want jobs whose published maximum compensation is at least this numeric amount. Units follow each listing's published pay cadence; omitted salaries do not match.

## Actor input object example

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

# Actor output Schema

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

Default dataset containing one normalized public Handshake job per item.

## `output` (type: `string`):

Compact machine-readable outcome with record, warning, and billing counts.

## `runSummary` (type: `string`):

Detailed search, job-page, validation, and billing diagnostics.

# 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": [
        "https://joinhandshake.com/find-jobs/remote/software-engineering/"
    ],
    "jobUrls": [],
    "maxItems": 5,
    "includeDescriptions": true,
    "titleIncludes": "",
    "companyIncludes": "",
    "remoteOnly": true,
    "minSalary": 0
};

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

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

```

## MCP server setup

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

```

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/h6fn4ZOPb1sfzNa3B/builds/S6SCroyCy7sIUXpoH/openapi.json
