# Tes.com UK Education Jobs Scraper (`santamaria-automations/tes-uk-scraper`) Actor

Extract teaching jobs from Tes.com, the UK's leading education job board. Returns title, employer, salary, teaching stage (primary/secondary/sixth form), subject, position type, contract, start date, closing date, and school type across UK and international schools.

- **URL**: https://apify.com/santamaria-automations/tes-uk-scraper.md
- **Developed by:** [NanoScrape](https://apify.com/santamaria-automations) (community)
- **Categories:** Jobs, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.00 / 1,000 serp results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## What's an Apify Actor?

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

## Tes.com UK Education Jobs Scraper

Scrape teaching jobs from [Tes.com](https://www.tes.com/jobs/), the UK's leading education recruitment platform. Paste a search URL or a keyword and location; get every listing back as structured JSON with employer, salary, teaching stage, subject, contract type, start date, closing date — and, unique to this actor, **both** the school-wide contact block AND the per-vacancy contact block that Tes exposes in its two on-page popups.

### What it does

Pulls live UK and international teaching vacancies from Tes.com search results, deduplicates them, and pushes them to your Apify dataset. Handles pagination automatically up to your result cap. Optionally opens each job's detail page to add structured salary ranges, ISO closing dates, full descriptions, and both contact popups. Optionally follows the employer profile page for About-us text, gallery images, school phase/funding/gender/age range and a list of every other open role at that school.

Fields you get per job:

- Job title, employer name, employer logo
- Location, country, salary (min, max, currency, period)
- Teaching stage (primary, secondary, sixth form, nursery)
- Subject (Maths, English, SEND, MFL, etc.)
- Position type (leadership, classroom, support, HLTA, SEN)
- Contract type (permanent, fixed-term, supply, maternity)
- Start date, closing date, working pattern
- School type (state, independent, academy, faith, special)
- Ofsted rating where mentioned in the ad
- Direct application URL
- School / employer website (from schema.org sameAs + `School contact details` popup)
- School postal address: street, city, postcode, country (from schema.org jobLocation.address)
- **`job_contact_email` + `job_contact_phone`** — per-vacancy contact info from the *Job contact details* popup (may differ from row to row at the same school)
- **`company_phone` + `company_website`** — employer-wide contact info from the *School contact details* popup
- With `includeCompanyDetails=true`: About-us text, gallery photos, school phase, funding status, gender intake, age range, denomination, and every other open role at the same employer

### Two contact popups, both extracted (unique to this actor)

Tes.com exposes two distinct contact blocks on every vacancy page and most competitor scrapers only grab one (or neither). This actor extracts both:

1. **School contact details** — employer-wide. Applies to every job posted by that school. Populates `company_website` and `company_phone`.
2. **Job contact details** — per-vacancy. May be a different email/phone for each vacancy at the same school (schools often route vacancies to specific recruiters). Populates `job_contact_email` and `job_contact_phone`.

If your outreach hits the wrong mailbox, applications get lost. Getting both means you can address the specific recruiter for a role and still fall back to the school's central contact when the per-vacancy one is blank.

### Sample output

```json
{
  "_type": "job",
  "id": "2339227",
  "title": "Primary Teacher",
  "employer_name": "The School of Research Science - SRS Dubai",
  "location": "Al Warqa 4, Dubai",
  "country": "AE",
  "salary_text": "Undisclosed",
  "teaching_stage": "primary",
  "position_type": "classroom",
  "start_date": "2026-09-01",
  "closing_at": "2026-08-30",
  "job_url": "https://www.tes.com/jobs/vacancy/primary-teacher-united-arab-emirates-2339227",
  "source_platform": "tes.com",

  "job_contact_email": "recruitment@srs.ae",
  "job_contact_phone": "4 6011011",

  "company_name": "The School of Research Science - SRS Dubai",
  "company_website": "http://www.srsdubai.ae/",
  "company_phone": "+971 4 6011011",
  "company_profile_url": "https://www.tes.com/jobs/employer/the-school-of-research-science---srs-dubai-1056353",
  "company_address_street": "Noakchott Street",
  "company_address_city": "Al Warqa 4",
  "company_address_country": "United Arab Emirates",
  "company_address_display": "Noakchott Street, Al Warqa 4, Dubai, United Arab Emirates",
  "company_phase": "All-through",
  "company_funding_status": "Independent",
  "company_gender": "Mixed",
  "company_age_range": "3 - 18 years",
  "company_school_type": "Mainstream School",
  "company_active_jobs_count": 4,
  "company_active_jobs": [
    {"title": "Curriculum Coordinator", "job_url": "https://www.tes.com/jobs/vacancy/curriculum-coordinator-united-arab-emirates-2338882"},
    {"title": "EYFS Class Teacher", "job_url": "https://www.tes.com/jobs/vacancy/eyfs-class-teacher-united-arab-emirates-2338912"}
  ]
}
```

### Pricing

Pay-per-event, so you only pay for what you use.

| Event | Rate |
|---|---:|
| Actor start | $0.001 per run |
| Job from search page | $0.004 per job |
| Job with detail-page enrichment | $0.006 per job |
| Company detail per unique employer (when `includeCompanyDetails: true`) | $0.005 per employer (deduplicated within a run) |

Apify's free plan includes $5 monthly platform credit that covers roughly 800 enriched jobs on this actor before any charge.

### Input

| Field | Type | Notes |
|---|---|---|
| `searchUrls` | array | Copy any Tes.com search URL. Filters preserved. |
| `searchQueries` | array | Keyword queries (for example `SEND teacher`, `head of maths`). |
| `location` | string | UK city, county or postcode. Applied to keyword queries. |
| `maxResults` | integer | Total cap across all queries. |
| `maxResultsPerQuery` | integer | Cap per individual query. |
| `includeJobDetails` | boolean | Open each PDP for structured salary, full description, and both contact popups. |
| `includeCompanyDetails` | boolean | Also follow `/jobs/employer/{slug}-{id}` for About text, gallery, phase, funding, and every other open role at the same school. Deduplicated per employer (charged once per unique school). Implies `includeJobDetails=true`. |
| `maxConcurrency` | integer | Concurrent detail requests (1 to 20). |
| `proxyConfiguration` | object | Residential proxy recommended. |

### Output fields

Every dataset row has a stable schema documented in the Output tab. Key derived fields:

- `teaching_stage`: primary, secondary, sixth-form, nursery, all-through
- `subject`: Maths, English, Science, SEND, MFL, PE, Art, Music, Drama, Computing, Business, Design & Technology, RE, Psychology, Sociology, History, Geography
- `position_type`: leadership, classroom, support, HLTA, SEN
- `contract_type`: permanent, fixed-term, supply, maternity
- `school_type`: state, independent, academy, faith, special
- `salary_range`: object with `min`, `max`, `currency`, `period`

**Per-vacancy contact (from Job contact details popup):**

- `job_contact_email`, `job_contact_phone`

**Employer / school profile (from School contact details popup + optional employer page):**

- `company_name`, `company_logo_url`, `company_website`, `company_phone`, `company_email`, `company_profile_url`
- `company_about`: full About-us text (requires `includeCompanyDetails=true`)
- `company_address_street`, `company_address_city`, `company_address_postcode`, `company_address_country`, `company_address_display`
- `company_phase`, `company_funding_status`, `company_gender`, `company_age_range`, `company_denomination`, `company_school_type`
- `company_gallery_images`, `company_header_image`
- `company_active_jobs_count`, `company_active_jobs` (requires `includeCompanyDetails=true`)

### Use with an AI agent (MCP)

Connect this actor to Claude, Cursor, or any MCP client to have your agent pull UK teaching jobs on demand.

```json
{
  "mcpServers": {
    "tes-uk-scraper": {
      "url": "https://mcp.apify.com/?actors=santamaria-automations/tes-uk-scraper"
    }
  }
}
```

Example prompt: "Find 20 SEND teacher jobs in Kent from Tes.com, only permanent contracts starting in September 2026, and return the school name, the school's central phone, and the per-vacancy recruiter email for each."

### Why this scraper

Built HTTP-only in Go for consistent speed and low cost. Uses schema.org JobPosting data on detail pages plus the Next.js hydration payload for the two contact popups, so both `job_contact_email` and `company_phone` come straight from Tes's own data model rather than screen-scraping rendered popups. Derives teaching stage, subject and school type from title and ad copy using UK curriculum vocabulary (KS1 through KS5, EYFS, SEND, MAT, faith).

### Common use cases

- Recruitment agencies tracking new vacancies daily and reaching out to the exact per-vacancy recruiter
- MATs and school groups benchmarking competitor pay bands
- Ed-tech vendors building account lists of UK schools by size, type and phase
- Teaching supply pools monitoring maternity and supply openings
- Universities running graduate outcomes research on NQT placements
- Direct-mail campaigns to school-wide addresses using verified postal + phone data

### Notes and limits

- Coverage: UK primary, secondary, sixth form, SEN and international schools that post on Tes.com
- Tes lists roles beyond the UK (Dubai, China, Cyprus, etc.); use the `location` field to narrow to UK cities if you only want domestic jobs
- Ofsted rating is only populated when the school explicitly names it in the ad
- Some employer branding pages return salary as "Undisclosed"; that carries through as a null salary range
- `job_contact_email` and `job_contact_phone` are only populated when the school has filled them in on that specific vacancy — some job types (e.g. leadership-recruiter-managed adverts) leave them blank
- `includeCompanyDetails=true` deduplicates by employer within a single run: 25 jobs at 5 unique schools = 5 `company-detail-result` charges, not 25
- Start dates like "ASAP" or "Immediate" are preserved verbatim rather than forced into ISO format

### Related Actors

- [Reed.co.uk Scraper](https://apify.com/santamaria-automations/reed-uk-scraper): UK jobs across all industries with salaries and full descriptions
- [Indeed Scraper](https://apify.com/santamaria-automations/indeed-scraper): Global job coverage with logos and benefits
- [CV-Library Scraper](https://apify.com/santamaria-automations/cvlibrary-scraper): UK recruitment agency and direct employer roles
- [NHS Jobs Scraper](https://apify.com/santamaria-automations/nhs-jobs-scraper): NHS vacancies including clinical and non-clinical roles
- [Website Email Scraper](https://apify.com/santamaria-automations/website-email-scraper): Turn a school website into a contact email

### Support

- Bug reports and feature requests: [Issues tab](https://apify.com/santamaria-automations/tes-uk-scraper/issues)
- Direct contact: contact@nanoscrape.com

We usually reply within 6 hours.

# Actor input Schema

## `searchQueries` (type: `array`):

One or more keyword queries (for example 'primary teacher', 'SEND teacher', 'head of maths'). Each runs as a separate search. Results are deduplicated.

## `location` (type: `string`):

UK city, county or postcode to filter by (for example 'London', 'Manchester', 'Kent'). Applied to all keyword searches.

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

Paste one or more Tes.com search URLs. Go to tes.com/jobs, run a search, and copy the URL. Filters like teaching stage, subject and location are preserved. Mutually exclusive with searchQueries.

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

Alias of searchUrls — direct SERP URLs to crawl instead of building from keywords. Mutually exclusive with searchQueries.

## `includeJobDetails` (type: `boolean`):

When true, opens each job's detail page for structured salary, dates and full description. Costs more but returns richer data.

## `includeCompanyDetails` (type: `boolean`):

Also fetch each unique employer's company profile page for the About-us text, phone, address, website, and a list of the employer's other currently-open roles. Deduplicated per employer within a run. Adds $0.005 per unique employer. Implies includeJobDetails=true (employer link only lives on the PDP).

## `maxResults` (type: `integer`):

Total cap across all queries and URLs.

## `maxResultsPerQuery` (type: `integer`):

Maximum results per search URL or keyword.

## `maxConcurrency` (type: `integer`):

Maximum concurrent detail page requests. Only used when Fetch job detail pages is on.

## Actor input object example

```json
{
  "location": "London",
  "searchUrls": [
    "https://www.tes.com/jobs/search/?keywords=primary+teacher&location=London"
  ],
  "includeJobDetails": false,
  "includeCompanyDetails": false,
  "maxResults": 10,
  "maxResultsPerQuery": 100,
  "maxConcurrency": 5
}
```

# Actor output Schema

## `jobListings` (type: `string`):

Dataset of UK teaching jobs. Each row has: id, title, employer, salary\_range (min/max/currency/period), teaching\_stage (primary/secondary/sixth-form/nursery), subject (Maths/English/SEND/etc), position\_type (leadership/classroom/support/HLTA/SEN), contract\_type (permanent/fixed-term/supply/maternity), start\_date, working\_pattern, description, job\_url, application\_url, posted\_at, closing\_at, school\_type (state/independent/academy/faith/special), ofsted\_rating, badges.

# 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://www.tes.com/jobs/search/?keywords=primary+teacher&location=London"
    ],
    "includeJobDetails": false,
    "maxResults": 10,
    "maxResultsPerQuery": 100,
    "maxConcurrency": 5
};

// Run the Actor and wait for it to finish
const run = await client.actor("santamaria-automations/tes-uk-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://www.tes.com/jobs/search/?keywords=primary+teacher&location=London"],
    "includeJobDetails": False,
    "maxResults": 10,
    "maxResultsPerQuery": 100,
    "maxConcurrency": 5,
}

# Run the Actor and wait for it to finish
run = client.actor("santamaria-automations/tes-uk-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://www.tes.com/jobs/search/?keywords=primary+teacher&location=London"
  ],
  "includeJobDetails": false,
  "maxResults": 10,
  "maxResultsPerQuery": 100,
  "maxConcurrency": 5
}' |
apify call santamaria-automations/tes-uk-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,santamaria-automations/tes-uk-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/SgztjIxM51uNe1EtP/builds/KDNsG4NnlkaFun3w4/openapi.json
