# Teaching Jobs Scraper (TES) - UK & International School Jobs (`vivid-softwares/tes-jobs-scraper`) Actor

Scrape teaching, school and education jobs from TES (tes.com/jobs), the UK and international teaching jobs board. Every job with salary, contract, closing date, requirements, how to apply and school details. Schedule it as a job alert: repeat runs return only new jobs.

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

## Pricing

$7.50 / 1,000 job listings

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?

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

### What does Teaching Jobs Scraper do?

**Teaching Jobs Scraper** collects **teaching, school and education jobs** from [TES](https://www.tes.com/jobs) (tes.com/jobs), the main **UK and international teaching jobs board**, and turns them into clean, structured data. Search by keyword, subject, role, school type, location, salary or contract, or paste any TES search URL, and get **every matching job**: title, school, salary as advertised plus **parsed minimum and maximum salary**, full or part time, contract term, start date, **closing date**, full description, **requirements**, **how to apply**, and **school details**: phase, type, funding, gender, age range, **number of pupils**, DfE number, awards, documents and inspection report.

Use it as a **teaching job search tool and job alert** (schedule it and get only the new jobs each day), a **school jobs API** for your own job board, or a **teacher recruitment data** feed. Run it from Apify Console, call it over the API, schedule it, or connect it to AI agents through MCP.

- 🍎 **Teacher jobs**: primary and secondary teachers, heads of department, SENCOs, early years teachers, lecturers and tutors
- 🏫 **School leadership jobs**: headteachers, deputy and assistant heads, principals, directors and CEOs
- 🧑‍💼 **School support jobs**: teaching assistants, cover supervisors, business managers, HR, IT, finance and facilities
- 🇬🇧 **UK school jobs** in England, Scotland, Wales and Northern Ireland: primary, secondary, independent, special, FE and nursery
- 🌍 **International teaching jobs** in the UAE, China, Thailand, Qatar, Spain and more than 70 other countries
- 💷 **Teacher salaries**: salary as advertised (MPS/UPS, TLR, pay scales) plus parsed minimum, maximum, currency and period
- 🔔 **New-job alerts**: repeat runs return only jobs you have not seen, plus a report of **closed and removed jobs**

### Why use this teaching jobs scraper?

- **Every job, not just the first page.** One search or URL returns every matching job, up to your limit. A run of the whole TES board (5,379 jobs) returned 5,379 of 5,379.
- **The same results as the site.** The Actor uses the search TES itself builds from your filters, so results match tes.com filter for filter. Checked against TES's own counts for 25 filter and location combinations.
- **Everything to decide and apply in one row**: salary, hours, contract, start date, closing date, requirements, the apply link and the school's recruitment contact.
- **School details included**: phase, type, funding status, gender, age range, pupil numbers, DfE number, religious character, awards, prospectus and inspection report, read once per school.
- **Requirements pulled out**: the lines from "About you", "Person specification", "Essential", "Qualifications" and similar sections of the advert, in their own field.
- **Only new jobs on repeat runs**: jobs you already received are skipped and not charged, so a daily schedule works as a job alert.
- **Fast**: about 1,000 full jobs in 30 to 45 seconds, and the whole TES board (about 5,400 jobs) in 2 to 4 minutes, depending on how quickly TES responds.

Common uses:

- **Teachers and school staff looking for a job**: a daily list of new jobs that match your subject, phase, location and salary, with closing dates and apply links, in a spreadsheet or your inbox
- **Education recruitment agencies**: fresh vacancies by subject, phase and region, with the school's recruitment contact
- **Job boards and job aggregators**: syndicate UK and international teaching jobs with structured, de-duplicated fields
- **Schools and multi-academy trusts**: benchmark salaries, TLRs and contract types against nearby schools
- **EdTech sales and marketing**: build school account lists by phase, type, size and funding from schools that are hiring
- **University careers services and teacher training providers**: show trainees and ECTs the jobs that suit them
- **Workforce and labour-market research**: track teacher demand by subject, region, contract term and ECT suitability
- **International school recruiters**: monitor overseas teaching jobs by country
- **AI agents**: structured teaching job data for assistants and automations

### How to scrape teaching jobs from TES

#### Option 1: paste a Tes Jobs URL (recommended)

1. Go to [tes.com/jobs](https://www.tes.com/jobs) and search. Add any filters: keyword, location and distance, position, subject, organisation type, contract term, full or part time, salary, ECT suitability, quick apply, sort order.
2. Copy the URL from your browser, for example `https://www.tes.com/jobs/search?keywords=maths&displayLocation=Manchester&…`.
3. Paste it into **Tes Jobs search, job or school URLs**, set **Maximum jobs** and click **Start**.

These Tes pages all work, and you can paste as many as you like in one run:

- Search results with any filters: `tes.com/jobs/search?…`
- Browse pages: `tes.com/jobs/browse/london`, `tes.com/jobs/browse/mathematics`, `tes.com/jobs/browse/international`
- School and employer profiles: `tes.com/jobs/employer/<school>`. You get every job that school currently advertises.
- Single job pages: `tes.com/jobs/vacancy/<job>`

#### Option 2: use the optional search builder

Fill in any of: **Keywords**, **Locations** (town, county, postcode, country or "International", typed as on Tes) with a **Search radius** of 3 to 500 miles, **Position**, **Subject**, **Organisation type / phase**, **Contract term**, **Full or part time**, **Minimum salary** with currency and period, **Suitable for ECTs only**, **Quick apply only** and **Sort order**. Each location is searched separately, and the log shows the matching tes.com URL so you can open the same search in your browser. URLs and builder searches can be combined; duplicates are removed.

Download results as JSON, CSV, Excel or HTML, or read them through the API.

### Input

The main input is `startUrls`. The other search fields belong to the optional search builder.

| Field | Description |
|---|---|
| `startUrls` | **Main input.** Any Tes Jobs search, browse, school profile or job URL. Every matching job is collected. |
| `keywords` | Job title or keyword, such as `maths teacher`, `SENCO` or `headteacher` |
| `locations` | Towns, counties, postcodes, countries or `International`, such as `Manchester`, `Kent`, `SW1A 1AA`, `Wales`, `United Arab Emirates` |
| `radius` | 3, 5, 10, 15, 20, 30 (Tes default), 50, 70, 100 or 500 miles |
| `positions` | Tes positions, either a whole group such as Leadership or single roles such as Teacher, Headteacher, Teaching Assistant |
| `subjects` | Tes subjects, such as Mathematics, English, Science, Physics, Modern Languages |
| `workplaces` | Organisation type or phase: Primary, Secondary, Special Education, Independent Senior, Independent Preparatory, Further Education, Nursery and more |
| `contractTerms` | Permanent, Fixed Term, Maternity Cover, Temporary, Casual, Supply |
| `contractTypes` | Full Time, Part Time |
| `minSalary`, `salaryCurrency`, `salaryFrequency` | Tes's salary filter, for example 40,000 GBP per year |
| `ectSuitable`, `quickApplyOnly` | Only jobs suitable for early career teachers; only jobs with Tes Quick apply |
| `sort` | Most relevant, newest or nearest |
| `maxItems` | Maximum number of saved jobs (default 100) |
| `includeSchoolProfile` | Add school details from each school's Tes profile (default on) |
| `includeApplicationContacts` | Include the recruitment email and phone Tes shows under "Job contact details" (default on) |
| `includeDescriptionHtml` | Also return the description as HTML (default off; plain text is always included) |
| `includeDuplicates` | Include jobs from previous runs (default off) |
| `reportExpired` | Report jobs that closed or were removed since your last run of the same search (default on, free) |
| `proxyConfiguration` | Not needed. The Actor connects directly and moves to Apify Proxy by itself if Tes starts refusing requests. |

#### Example input: Tes URLs

```json
{
  "startUrls": [
    { "url": "https://www.tes.com/jobs/search?keywords=teacher&subjects=Mathematics&workplaces=Secondary&distanceUnit=mi&sort=date" },
    { "url": "https://www.tes.com/jobs/browse/international" },
    { "url": "https://www.tes.com/jobs/employer/glebedale-school-1174003" }
  ],
  "maxItems": 500
}
```

#### Example input: search builder

```json
{
  "keywords": "teacher",
  "locations": ["Leeds", "Manchester", "SW1A 1AA"],
  "radius": "10",
  "subjects": ["Mathematics", "Physics"],
  "workplaces": ["Secondary"],
  "contractTerms": ["Permanent"],
  "ectSuitable": true,
  "sort": "date",
  "maxItems": 1000
}
```

### Output

Each job is one dataset item. Fields that Tes does not provide for a job are left out rather than guessed.

#### Example job

```json
{
  "job_id": "2347018",
  "url": "https://www.tes.com/jobs/vacancy/maths-lead-stoke-on-trent-2347018",
  "title": "Maths Lead",
  "employer_name": "Glebedale School",
  "employer_id": "1174003",
  "employer_profile_url": "https://www.tes.com/jobs/employer/glebedale-school-1174003",
  "employer_logo_url": "https://l.imgt.es/employer-assets/employers/1174003/brandimages/20201016_1205PM_desktop_1174003_logo.jpg?profile=employer-logo",
  "employer_website": "https://www.outcomesfirstgroup.co.uk/",
  "employer_phone": "+44 1782 320773",
  "school_type": "Special Needs",
  "school_phase": "Special Needs",
  "funding_status": "Independent",
  "school_gender": "Mixed",
  "age_range": "7 - 19 years",
  "age_from": 7,
  "age_to": 19,
  "number_of_pupils": 36,
  "dfe_number": "8616004",
  "workplaces": ["Special Education"],
  "positions": ["Subject Leader"],
  "subjects": ["Mathematics"],
  "display_location": "Stoke-on-Trent",
  "address": "Grove Road, Heron Cross, Stoke-on-Trent, Staffordshire, ST4 3AY, United Kingdom",
  "town": "Stoke-on-Trent",
  "county": "Staffordshire",
  "postcode": "ST4 3AY",
  "country": "United Kingdom",
  "region": "West Midlands",
  "latitude": 52.99157,
  "longitude": -2.167841,
  "salary_text": "Up to £45,000 per annum (depending on experience)",
  "salary_min": 45000,
  "salary_max": 45000,
  "salary_currency": "GBP",
  "salary_period": "year",
  "salary_source": "parsed_from_text",
  "contract_terms": ["Permanent"],
  "contract_types": ["Full Time"],
  "job_type": "Full Time, Permanent",
  "employment_type": "FULL_TIME",
  "hours_per_week": 37.5,
  "date_posted": "2026-09-24T23:00:00.000Z",
  "closing_date": "2026-10-16T22:55:00.000Z",
  "apply_by": "2026-10-16",
  "job_status": "open",
  "suitable_for_ects": false,
  "description": "We work smarter so that you can enjoy the things that matter outside of work\nJob Title: Maths Lead (Secondary)\nLocation: Glebedale School, Stoke-on-Trent, ST4 3AY\nHours: 37.5 per week | …",
  "requirements": [
    "UK QTS or equivalent",
    "Strong subject knowledge across number, algebra, geometry, statistics and problem‑solving.",
    "…"
  ],
  "application_method": "Apply on the employer website",
  "application_url": "https://candidate.ofgeducationcare.co.uk/325640OTES",
  "quick_apply": false,
  "application_contact_name": "Glebedale School",
  "application_email": "recruitment@example.org",
  "application_phone": "01782 320773",
  "job_documents": [
    { "title": "Inspection report", "url": "https://d3giikteahxfyn.cloudfront.net/employers/1174003/attachments/Glebedale-Ofsted.pdf", "size": "170.94KB" },
    { "title": "School prospectus", "url": "https://d3giikteahxfyn.cloudfront.net/employers/1174003/attachments/glebedale-school-prospectus.pdf", "size": "5.19MB" }
  ],
  "inspection_report_url": "https://d3giikteahxfyn.cloudfront.net/employers/1174003/attachments/Glebedale-Ofsted.pdf",
  "ofsted_mentions": ["Based in Stoke-on-Trent and rated ‘Good’ by Ofsted, the school is proud to provide a safe, stimulating and nurturing environment, …"],
  "employer_open_jobs": 3,
  "employer_profile_complete": true,
  "listing_tier": "gold",
  "advert_package": "premium",
  "search_url": "https://www.tes.com/jobs/search?keywords=maths&distanceUnit=mi&sort=relevance",
  "seen_in_previous_run": false,
  "scraped_at": "2026-09-28T15:00:00+00:00"
}
```

#### Output fields

| Group | Fields |
|---|---|
| Job | `job_id`, `url`, `title`, `positions`, `subjects`, `workplaces`, `listing_tier`, `advert_package`, `search_url` |
| Terms | `job_type` (as Tes shows it, for example Full Time, Fixed Term), `contract_terms`, `contract_types`, `employment_type`, `hours_per_week`, `fte`, `start_date`, `suitable_for_ects` |
| Salary | `salary_text` (as advertised), `salary_range_text`, `salary_min`, `salary_max`, `salary_currency`, `salary_period`, `salary_source` |
| Dates | `date_posted`, `closing_date`, `apply_by` (the date Tes shows), `advert_end_date`, `job_status` (open, closed or advert\_ended) |
| Content | `short_description`, `description`, `description_html` (optional), `requirements`, `job_documents` |
| How to apply | `application_method`, `application_url`, `quick_apply`, `application_contact_name`, `application_email`, `application_phone` |
| Location | `display_location`, `address`, `street`, `town`, `county`, `postcode`, `country`, `region`, `local_area`, `latitude`, `longitude`, `google_maps_url` |
| School | `employer_name`, `employer_id`, `employer_profile_url`, `employer_website`, `employer_phone`, `employer_logo_url`, `employer_header_image_url`, `school_type`, `school_phase`, `funding_status`, `school_gender`, `age_range`, `age_from`, `age_to`, `religious_character`, `number_of_pupils`, `dfe_number`, `charity_number`, `company_registration_number`, `employer_group`, `is_school_group`, `employer_about`, `employer_awards`, `employer_photos`, `employer_videos`, `employer_links`, `employer_documents`, `inspection_report_url`, `ofsted_mentions`, `employer_open_jobs`, `employer_profile_complete` |
| Run | `seen_in_previous_run`, `scraped_at` |

The Output tab has three views: **Jobs**, **Schools** and **Salaries**. A `RUN_SUMMARY` record in the key-value store lists job URLs that are no longer on Tes (`unavailable_urls`) and any that could not be loaded (`failed_urls`). The `EXPIRED_JOBS` record holds the expired jobs report (see below).

About salaries: `salary_min` and `salary_max` come from Tes's own salary range when the school gives one (`salary_source: tes_salary_range`). Otherwise the Actor reads the salary text, using only a clearly stated figure or range and the period written next to it, and skipping TLRs, allowances and bonuses (`parsed_from_text`). Pay scales without figures, such as "MPS/UPS", keep `salary_text` only.

About Ofsted: Tes has no Ofsted rating field. `inspection_report_url` is the report the school attached to its Tes profile, and `ofsted_mentions` quotes the sentences about Ofsted from the school's profile and the job advert, word for word.

### New-job alerts, pagination and duplicates

- The Actor reads each search through Tes's own results pages, 50 jobs at a time and several pages at once, until it reaches the end of the results or your **Maximum jobs**. There is no page limit: a search of the whole Tes board returned all 5,379 jobs.
- **Within a run**, each job is saved once, even if it appears in several searches.
- **Across runs**, the Actor remembers the jobs it has already saved for your account and skips them by default, so scheduled runs only return and charge for **new jobs**. Turn on **Include jobs from previous runs** to get everything again; `seen_in_previous_run` tells you which ones you had before. The history is kept in a dataset named `tes-jobs-history-<your user ID>` in your own Apify account.

### Expired and removed jobs report

Scheduled runs also tell you which jobs **disappeared** since the last run of the same search, so you can close them in your own job board or CRM.

- The Actor remembers every job each search or school URL returned. On the next run of the same search, jobs that are no longer in the results are saved to the `EXPIRED_JOBS` record in the run's key-value store (also linked from the Output tab).
- Each one is looked up on Tes again and gets a `reason`: `closed` (the application deadline passed), `advert_ended`, `removed_from_tes` (the job no longer exists), or `no_longer_matches_search` (still open, but out of the search, for example after the school edited it).
- Every entry keeps the `job_id`, `url`, `title`, `employer_name`, `closing_date`, `last_seen_at` and the `search_url` it came from.
- The report is free: it is not charged as results. It needs the whole search to be read, so set **Maximum jobs** above the number of results. Searches cut short by the limit are skipped for that run and compared again next time.

### Teaching jobs API: use the scraper through the API

```bash
curl -X POST "https://api.apify.com/v2/acts/vivid-softwares~tes-jobs-scraper/run-sync-get-dataset-items?token=YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"startUrls": [{"url": "https://www.tes.com/jobs/browse/london"}], "maxItems": 20}'
```

#### Python

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_API_TOKEN")
run = client.actor("vivid-softwares/tes-jobs-scraper").call(run_input={
    "keywords": "science teacher",
    "locations": ["Birmingham"],
    "radius": "20",
    "workplaces": ["Secondary"],
    "maxItems": 200,
})
for job in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(job["title"], "-", job["employer_name"], "-", job.get("salary_text"), "- closes", job["closing_date"][:10])
```

#### JavaScript

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: 'YOUR_API_TOKEN' });
const run = await client.actor('vivid-softwares/tes-jobs-scraper').call({
    startUrls: [{ url: 'https://www.tes.com/jobs/browse/international' }],
    maxItems: 500,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.map((job) => `${job.title} at ${job.employer_name}, ${job.country} (${job.school_phase ?? 'phase n/a'})`));
```

### Teaching job data for AI agents and MCP

The Actor works with the [Apify MCP server](https://mcp.apify.com), so assistants such as Claude, ChatGPT or Cursor can call it as a tool. Add `vivid-softwares/tes-jobs-scraper` to your MCP configuration and ask, for example:

- "List permanent secondary maths teacher jobs within 10 miles of Leeds that are suitable for ECTs."
- "Which independent schools in Surrey are hiring, and how many pupils do they have?"
- "Show international school jobs in the UAE posted this week with salaries in AED."

### How this Tes Jobs scraper compares

We checked every other Tes Jobs Actor in Apify Store in September 2026, using their Store pages. This is what you get here that you won't find in one place anywhere else:

| What you get | This Actor | Other Tes Jobs Actors in Apify Store |
|---|:---:|:---:|
| Paste any Tes search URL and keep every filter | ✅ | 1 of 3 |
| Search builder with Tes's position, subject, organisation type and contract filters | ✅ | 1 of 3 |
| Salary, ECT-suitable and Quick apply filters | ✅ | None |
| Location by town, postcode or country name, with Tes's own radius | ✅ | None |
| Paste a school's Tes profile to get all of its jobs | ✅ | None |
| School phase, funding and age range on every job | ✅ | 2 of 3 |
| Pupil numbers, DfE number, awards and school documents | ✅ | None |
| Requirements (person specification, qualifications) in their own field | ✅ | None |
| ECT suitability for each job | ✅ | None |
| Only new jobs on repeat runs, and skipped jobs cost nothing | ✅ | None |
| Expired and removed jobs report with the reason | ✅ | None |
| Pay per job only, no start fee | ✅ | None |

In short: one run gives you the jobs (every one, in full), the schools behind them (size, phase, funding and contacts), and what changed since last time (new jobs only, plus closed and removed jobs), at $7.50 per 1,000 jobs.

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

This Actor uses **pay-per-event** pricing: **$7.50 per 1,000 jobs**, which is $0.0075 for each job saved to the dataset. There is no start fee, and school profiles are included. You are not charged for search pages, school profiles, retries, invalid input, jobs that are no longer on Tes, or jobs skipped because you already received them. For example, all 5,400 or so jobs on Tes cost about $40, and a daily run that finds 150 new jobs costs about $1.13.

### Tips

- Paste the URL you see on Tes: every filter in it is applied by Tes itself, so the results match the site exactly.
- **Speed:** at the default 2 GB memory the Actor reads about 1,000 full jobs in 30 to 45 seconds, depending on how quickly Tes responds. At 512 MB the same run takes about 100 seconds and uses slightly less compute.
- Turn off **Add school profile details** if you only need job fields. Profiles are read once per school, so they add little time.
- Schedule a daily run of your searches: only new jobs are returned and charged, and `EXPIRED_JOBS` lists the ones that closed.
- International jobs: use `https://www.tes.com/jobs/browse/international`, or type a country such as `Spain` or `United Arab Emirates` in **Locations**.

### Data limitations

- Tes shows `salary_min` and `salary_max` only when the school gives figures. About 4 in 10 jobs give a pay scale or "competitive" instead.
- Pupil numbers, DfE numbers, awards and documents come from the school's Tes profile and are present only when the school filled them in. Some employers have no public Tes profile; their jobs have `employer_profile_complete: false`.
- `requirements` is filled when the advert has a clear requirements section, such as "Person specification", "Essential" or "About you" (more than a third of adverts).
- Tes has no Ofsted rating field. Use `inspection_report_url` and `ofsted_mentions`.

### Public contact data policy

The Actor only returns contact details that Tes shows publicly:

- For the school: the **website and main phone number** shown on its Tes profile. The school's email address is never returned, because Tes does not show it.
- For each job: the recruitment **email and phone Tes shows under "Job contact details"**, which is the official way to apply. Some schools list a named member of staff there; you can leave these fields out with **Include job contact details**.
- Never returned: the Tes user who posted the job, candidate data, or anything behind a login.

If you use contact data for outreach, follow UK GDPR, PECR and the data protection rules of the country you work in.

### FAQ

**What is the best teaching jobs scraper on Apify?** It depends on the job board you need. For TES, the main UK and international teaching jobs board, this is the most complete option (see the comparison above). Other Actors cover other boards, such as the Department for Education's Teaching Vacancies service for English state schools or EdJoin for US schools.

**Can I get alerts for new teaching jobs?** Yes. Save your search (a TES URL or search builder filters) as an Apify task and schedule it daily. Each run returns only the jobs you have not received before, and you only pay for those. Send them to Google Sheets, Slack, email or Zapier with Apify integrations.

**Can I use it to look for a teaching job myself?** Yes. Filter by subject, role, phase, location and radius, salary, contract or ECT suitability, and you get one list with the salary, closing date, requirements and apply link for every matching job.

**What is the best TES Jobs scraper on Apify?** For complete data, this one. It is the only Tes Jobs Actor in Apify Store that combines pasted Tes search URLs with a search builder covering Tes's salary, ECT and Quick apply filters, adds pupil numbers, DfE numbers and requirements to every job, returns only new jobs on repeat runs, and reports which jobs closed or were removed. See the comparison above.

**Is there an official Tes Jobs API?** Tes does not offer a public jobs API. This Actor gives you structured Tes job data through the Apify API.

**Is it legal to scrape Tes Jobs?** The Actor collects publicly available job information, does not log in, and paces its requests. You are responsible for how you use the data, including data protection rules. If in doubt, ask a lawyer.

**Does it cover international schools?** Yes. Tes's UK board includes every international job, with salaries in local currencies. Use the International browse page or type a country in **Locations**.

**Can I search by postcode?** Yes. Type a postcode such as `SW1A 1AA` in **Locations** and pick a radius, or paste a Tes URL where you searched by postcode.

**Does it get more than the first page of results?** Yes. It collects every matching job up to your limit.

**Does it return duplicate jobs?** No. Jobs are de-duplicated within a run, and jobs from your previous runs are skipped by default.

**Can you add a field or a custom version?** Open an issue on the Issues tab.

*This Actor is not affiliated with, endorsed by or connected to Tes Global Ltd.*

# Actor input Schema

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

The easiest way to use this Actor: search on <a href="https://www.tes.com/jobs" target="_blank">tes.com/jobs</a> with any filters, then paste the results URL here. Every matching job is collected, not just the first page. Also works with browse pages (tes.com/jobs/browse/london), single job pages and school profiles (tes.com/jobs/employer/…) for all of a school’s jobs. Leave empty to use the search builder below.

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

Optional search builder, used instead of or as well as URLs. Job title or keyword, for example "maths teacher", "SENCO" or "headteacher". The builder runs when any of its fields is filled in.

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

Towns, counties, postcodes, countries or "International", typed as you would on Tes (for example "Manchester", "Kent", "SW1A 1AA", "Wales", "United Arab Emirates", "International"). Each location is searched separately. Leave empty for all UK and international jobs.

## `radius` (type: `string`):

Distance around each location, as in Tes’s own Distance box.

## `positions` (type: `array`):

Tes positions. Pick a group such as "Leadership (all)" or single roles such as Teacher or Headteacher.

## `subjects` (type: `array`):

Tes subjects.

## `workplaces` (type: `array`):

School phase and type as Tes files them: Primary, Secondary, Special Education, Independent Senior, Further Education, Nursery and more.

## `contractTerms` (type: `array`):

Permanent, fixed term, maternity cover and so on.

## `contractTypes` (type: `array`):

Working pattern.

## `minSalary` (type: `integer`):

Only jobs whose Tes salary range reaches at least this amount (Tes’s own salary filter). Jobs that only give a pay scale such as MPS/UPS are left out by Tes when this is set.

## `salaryCurrency` (type: `string`):

Currency of the minimum salary (used only with Minimum salary).

## `salaryFrequency` (type: `string`):

Period of the minimum salary (used only with Minimum salary).

## `ectSuitable` (type: `boolean`):

Only jobs marked suitable for early career teachers (NQTs/ECTs).

## `quickApplyOnly` (type: `boolean`):

Only jobs with Tes Quick apply.

## `sort` (type: `string`):

Order of results.

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

Stop after saving this many jobs. Each saved job is one billable result.

## `includeSchoolProfile` (type: `boolean`):

Reads each school’s Tes profile once per run for pupil numbers, phase, type, funding, gender, age range, religious character, DfE number, phone, awards, photos, documents and inspection report.

## `includeApplicationContacts` (type: `boolean`):

The recruitment email and phone Tes shows publicly under "Job contact details". Some schools list a named member of staff here. Turn off to leave them out.

## `includeDescriptionHtml` (type: `boolean`):

Also return the job description as HTML. The plain-text description is always included.

## `includeDuplicates` (type: `boolean`):

Off by default: jobs you already received in an earlier run are skipped and not charged, so scheduled runs only return new jobs. Turn on to get every matching job again.

## `reportExpired` (type: `boolean`):

Remembers every job each search returns. On the next run of the same search, jobs that have gone are saved to the EXPIRED\_JOBS record with the reason: closed (deadline passed), advert ended, removed from Tes, or no longer matching. Free. Works when the whole search is read, so set Maximum jobs above the search size.

## `proxyConfiguration` (type: `object`):

Not needed: the Actor connects directly and moves to Apify Proxy by itself if Tes starts refusing requests.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://www.tes.com/jobs/search?keywords=maths&distanceUnit=mi&sort=relevance"
    }
  ],
  "radius": "30",
  "salaryCurrency": "",
  "salaryFrequency": "",
  "ectSuitable": false,
  "quickApplyOnly": false,
  "sort": "relevance",
  "maxItems": 100,
  "includeSchoolProfile": true,
  "includeApplicationContacts": true,
  "includeDescriptionHtml": false,
  "includeDuplicates": false,
  "reportExpired": true,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

No description

## `expired` (type: `string`):

No description

## `runSummary` (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 = {
    "startUrls": [
        {
            "url": "https://www.tes.com/jobs/search?keywords=maths&distanceUnit=mi&sort=relevance"
        }
    ],
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("vivid-softwares/tes-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 = {
    "startUrls": [{ "url": "https://www.tes.com/jobs/search?keywords=maths&distanceUnit=mi&sort=relevance" }],
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("vivid-softwares/tes-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 '{
  "startUrls": [
    {
      "url": "https://www.tes.com/jobs/search?keywords=maths&distanceUnit=mi&sort=relevance"
    }
  ],
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call vivid-softwares/tes-jobs-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,vivid-softwares/tes-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/0uBgEPU576rDBT0MH/builds/f9n9R6uIQHG0imedq/openapi.json
