# EdJoin Scraper (`crawlerbros/edjoin-scraper`) Actor

Scrape K-12 education job postings from EdJoin.org - California's largest education jobs board - search by keyword, category, job type, employment type, or fetch exact postings by ID/URL.

- **URL**: https://apify.com/crawlerbros/edjoin-scraper.md
- **Developed by:** [Crawler Bros](https://apify.com/crawlerbros) (community)
- **Categories:** Agents, Jobs, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 results

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.
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 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

## EdJoin Scraper

Scrape K-12 education job postings from **EdJoin.org** — California's largest education jobs board, used by public school districts, county offices of education, and charter/private schools across California (and a small number of postings from other US states/Canadian provinces). Search by keyword, job category, job type/role, employment type, or fetch exact postings by ID or URL. Every job includes the full job summary, requirements/qualifications, required documents, contact details, and employer profile.

### What this actor does

- **Three modes:** `search` (keyword + filter search), `byPostingIds` (exact lookup by numeric posting ID), `byUrls` (exact lookup by job page URL)
- **Rich filters:** job category (Certificated / Certificated Management / Classified / Classified Management), job type/role (66 specific roles), employment type (full/part time), state, specific school district (by numeric district ID), online-application availability, posted-within-days window, and sort order
- **Full posting detail:** complete job summary, requirements/qualifications, required application documents, number of openings, length of work year, employer description and logo, and hiring-contact name/email/phone
- **Empty fields are omitted** — every record only contains data that was actually found (district admins fill in these fields inconsistently, so the actor reports exactly what's published, nothing invented)

### Output per job

| Field | Description |
|---|---|
| `postingId` | Numeric EdJoin posting ID |
| `positionTitle` | Job title |
| `districtName`, `districtUrl`, `districtLogoUrl` | Hiring school district/organization |
| `city`, `state`, `countyName`, `zip` | Location |
| `categoryId`, `categoryName` | Top-level job category |
| `jobTypeId`, `jobType` | Specific job type/role |
| `employmentType` | Full Time / Part Time |
| `salaryInfo`, `payRangeFrom`, `payRangeTo`, `payRangePeriod`, `singleRate`, `singleRatePeriod` | Salary as advertised |
| `numberOpenings` | Number of open positions |
| `lengthOfWorkYear` | Work-year schedule text |
| `jobSummary` | Plain-text job definition/summary |
| `requirements` | Plain-text requirements/qualifications |
| `requiredDocuments[]` | Documents applicants must submit |
| `employerDescription` | About-the-employer text |
| `contactName`, `contactEmail`, `contactPhone` | Hiring contact |
| `onlineApp` | Whether online application is available on EdJoin |
| `isSummerSchool` | Summer-school flag |
| `postedDate`, `applicationDeadline`, `createdAt` | Key dates (ISO 8601 UTC) |
| `postingUrl` | Canonical job posting URL |
| `recordType` | Always `"job"` |
| `scrapedAt` | UTC scrape timestamp |
| `sourceUrl` | Same as `postingUrl` |

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `mode` | select | `search` | `search` / `byPostingIds` / `byUrls` |
| `keywords` | string | `teacher` | Free-text search query (mode=search) |
| `category` | select | `0` (All) | Job category |
| `jobTypeIds` | array (enum) | – | Filter by specific job type/role |
| `employmentType` | select | – | `Full` / `Part` |
| `states` | array (enum) | – | Filter by posting's state/province (client-side; see FAQ) |
| `counties` | array (enum) | – | Filter by posting's California county, e.g. `Los Angeles`, `San Diego`, `Alameda` (client-side; see FAQ) |
| `districtId` | integer | – | Filter to one specific school district by its numeric EdJoin district ID (server-side) |
| `onlineAppsOnly` | boolean | `false` | Only postings that accept online applications |
| `postedWithinDays` | select | `0` (any time) | `1` / `3` / `7` / `14` / `30` days |
| `sortBy` | select | `postingDate` | `postingDate` / `positionTitle` / `districtName` |
| `sortOrder` | select | `desc` | `asc` / `desc` |
| `postingIds` | array | – | Numeric posting IDs (mode=byPostingIds) |
| `startUrls` | array | – | Job page URLs (mode=byUrls) |
| `fetchFullDetails` | boolean | `true` | Fetch each posting's detail page for the richer field set above |
| `maxItems` | integer | `50` | Maximum number of jobs to return (1–2000) |

#### Example: California teaching vacancies, posted this week

```json
{
  "mode": "search",
  "keywords": "teacher",
  "category": "1",
  "postedWithinDays": "7",
  "sortBy": "postingDate",
  "maxItems": 50
}
```

#### Example: fetch specific postings by ID

```json
{
  "mode": "byPostingIds",
  "postingIds": [2260175, 2259549]
}
```

#### Example: fetch specific postings by URL

```json
{
  "mode": "byUrls",
  "startUrls": [{ "url": "https://www.edjoin.org/Home/JobPosting/2260175" }]
}
```

### Use cases

- **Education recruitment agencies** monitoring new California teaching/classified vacancies by role
- **Job aggregators** syndicating California K-12 job postings
- **Labour-market researchers** tracking district hiring volume by job type/category
- **District HR teams** benchmarking postings, salaries, and openings against peer districts

### FAQ

**Does this require login or an API key?**
No. EdJoin.org job search and job postings are public; no account, cookies, or API key are needed.

**What geography does this cover?**
EdJoin.org is overwhelmingly California-focused — the vast majority of postings are from California school districts and county offices of education, with an occasional posting from another US state or Canadian province. The `states` and `counties` filters are applied client-side against each posting's own listed state/county (EdJoin's own state/region search parameters do not reliably filter server-side), so filtering to a non-California state will typically return few or no results — that reflects the real composition of the job board, not a bug in the actor.

**Why do some jobs have fewer fields than others?**
EdJoin lets each school district fill in whichever detail-page sections it wants (job summary, requirements, required documents, etc. are all optional per posting). The actor reports exactly what each district published — it never fabricates a missing field.

**How fresh is the data?**
Every run queries EdJoin.org's live job-search endpoint directly — there is no caching layer.

**Why does `fetchFullDetails` matter?**
The search-results endpoint only returns a compact summary (title, district, location, salary, dates). Enabling `fetchFullDetails` (on by default) fetches each job's detail page for the job summary, requirements, required documents, employer profile, and hiring contact — at the cost of one extra request per job.

# Actor input Schema

## `mode` (type: `string`):

search = keyword/filter job search (default). byPostingIds = look up exact postings by numeric ID. byUrls = fetch exact postings by URL.

## `keywords` (type: `string`):

Free-text search query — job title or keyword (e.g. "teacher", "custodian", "school psychologist"). Leave empty to browse all current postings.

## `category` (type: `string`):

Filter by top-level job category.

## `jobTypeIds` (type: `array`):

Filter by specific job type/role. Leave empty for all job types. Note: a few labels repeat across categories (e.g. "Coach") and are disambiguated with their internal ID.

## `employmentType` (type: `string`):

Filter by full-time / part-time.

## `districtId` (type: `integer`):

Filter to postings from one specific school district/employer, by its numeric EdJoin district ID. Leave empty for all districts.

## `states` (type: `array`):

Filter by employer's state/province (matched against the posting's own listed state). EdJoin postings are overwhelmingly California-based; other states/provinces occasionally appear. Leave empty for all.

## `counties` (type: `array`):

Filter by California county (matched against the posting's own listed county). Leave empty for all counties.

## `onlineAppsOnly` (type: `boolean`):

Only include postings that accept online applications through EdJoin.

## `postedWithinDays` (type: `string`):

Only include postings created within this many days.

## `sortBy` (type: `string`):

Order of search results.

## `sortOrder` (type: `string`):

Ascending or descending order for the chosen sort field.

## `postingIds` (type: `array`):

Exact numeric EdJoin posting IDs to fetch (found in the job URL, e.g. 2260175).

## `startUrls` (type: `array`):

Exact EdJoin job posting page URLs to fetch (e.g. https://www.edjoin.org/Home/JobPosting/2260175).

## `fetchFullDetails` (type: `boolean`):

When enabled (mode=search), fetches each posting's detail page (job summary, requirements/qualifications, required documents, contact info, number of openings, length of work year, employer description/logo) in addition to the search-result summary. Adds one extra request per job. Always enabled for byPostingIds/byUrls modes.

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

Maximum number of job postings to return.

## Actor input object example

```json
{
  "mode": "search",
  "keywords": "teacher",
  "category": "0",
  "jobTypeIds": [],
  "employmentType": "",
  "states": [],
  "counties": [],
  "onlineAppsOnly": false,
  "postedWithinDays": "0",
  "sortBy": "postingDate",
  "sortOrder": "desc",
  "postingIds": [],
  "startUrls": [],
  "fetchFullDetails": true,
  "maxItems": 20
}
```

# Actor output Schema

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

Dataset containing all scraped EdJoin.org job postings.

# 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 = {
    "mode": "search",
    "keywords": "teacher",
    "category": "0",
    "employmentType": "",
    "postedWithinDays": "0",
    "sortBy": "postingDate",
    "sortOrder": "desc",
    "maxItems": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("crawlerbros/edjoin-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 = {
    "mode": "search",
    "keywords": "teacher",
    "category": "0",
    "employmentType": "",
    "postedWithinDays": "0",
    "sortBy": "postingDate",
    "sortOrder": "desc",
    "maxItems": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("crawlerbros/edjoin-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 '{
  "mode": "search",
  "keywords": "teacher",
  "category": "0",
  "employmentType": "",
  "postedWithinDays": "0",
  "sortBy": "postingDate",
  "sortOrder": "desc",
  "maxItems": 20
}' |
apify call crawlerbros/edjoin-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,crawlerbros/edjoin-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/22IWAATsd5wGdgwOs/builds/aEJEr5mEfzRdarvgd/openapi.json
