The Muse Jobs $0.49/1K — Remote Job Board, Education Jobs
Pricing
from $0.49 / 1,000 jobs
The Muse Jobs $0.49/1K — Remote Job Board, Education Jobs
$0.49/1K | 411,812 jobs on The Muse — the public API stops at 1980 per query; this splits the search to pass it. Full job description text inline, no extra call per job. Software engineering jobs, entry level jobs, sales jobs, retail jobs, marketing jobs, customer service jobs, remote work listings.
Pricing
from $0.49 / 1,000 jobs
Rating
0.0
(0)
Developer
Snow Leo Data
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
3 days ago
Last modified
Categories
Share
The Muse Jobs Scraper — 411K jobs, full text, past the 1,980 cap
$0.49 per 1,000 jobs — half the price of the other job scrapers in this store, because this source hands over the entire job advert text in the search response. No second request per job, so a run is both faster and cheaper.
| 411,813 jobs on The Muse | the API's own counter, read live on the day this page was last updated |
| The public API stops at 1,980 per query | measured: page 99 is the last with data, page 100 returns HTTP 400, and the page size is fixed at 20 |
| This Actor goes past it | measured: 2,060 unique jobs in 176 s from one run, zero duplicates |
| 27 fields | city, state and country parsed out of the location; emails and links pulled from the advert body |
| Full advert inline | 5,000-character bodies arrive with the listing, not behind a second call |
The 1,980 wall
Ask for page 100 and the API answers HTTP 400. The page size is fixed at 20 no matter what you pass — send items_per_page=50 and you still get 20 rows back. So one query can never hand you more than 1,980 jobs, out of 411,813. A scraper that simply pages through a search reaches 0.48% of this curated job board.
This Actor splits a wide search until every slice fits under the wall: first by category, then by seniority, then by location. The values are read from the source's own results — The Muse publishes no taxonomy endpoint, so inventing a category list would mean inventing data. Slices overlap; every job is deduplicated by id during the walk.
walk: 2060 unique jobs in 176 s, 30 queries, split by category, level, location
Who this is for
Anyone building a job board. The advert text arrives with the listing, so a posting can be republished without a second fetch, and the landing page URL is right there.
Recruiters watching a market. Turn on Only jobs not seen before: delivered jobs are remembered in a named store that survives between runs, and a daily schedule charges you only for what appeared since yesterday.
AI and agent pipelines. Compact output returns 14 core fields and no advert body — the shape an LLM actually consumes — and Drop empty fields removes what the employer never filled in.
Quick start
Press Start. With no filters you get a 300-job sample of the newest listings.
{"categories": ["Software Engineering"],"levels": ["Senior Level"],"locations": ["New York, NY"],"maxItems": 0}
What a row lets you filter on
27 fields per job; the full list is under Fields in detail. Three of them decide what a run is worth to you. The Muse publishes a location as one string, and this Actor turns it into job location data — city, state and country as separate fields, by a rule spelled out further down, so New York jobs separate from New Jersey without matching strings. Job level data is the board's own seniority band rather than a reading of the title, and job tags data is the set of labels the employer itself attached to the advert.
Job category data and seniority, as fields
The Muse files every advert under exactly one category and tags it with a seniority band, so job category data and job seniority data arrive as fields instead of as something you infer from the title. That is what turns a slice like internship listings in software engineering, or remote job listings in healthcare, into a filter rather than a keyword guess.
Internship listings here are the board's own Internship tag, not adverts that
happen to contain the word intern: internship jobs data and senior jobs data are
two values of the same level column, so one filter gives you either. Remote
job listings are the ones whose location entry reads Flexible / Remote, not the
ones whose advert text says "no remote work".
The Muse sells employer branding — companies pay to be profiled here — so what comes back is employer branding data as much as a job feed: the employer list is self-selected, which is why tech company jobs and large-employer adverts are over-represented and small local shops are not here at all.
Remote as a field, not a filter
is_remote is a column on every row, so a run doubles as remote work data:
which companies post remote roles, in which of the 32 categories and at which
seniority. Filter it after the fact instead of guessing from the location
string: work from home data and US remote jobs are a cut across the whole board,
not a corner of it. Remote hiring data therefore needs no separate run — it is
one column on the rows you already paid for.
Category names are The Muse's own, not ours: data science jobs sit under
Data and Analytics, social work under Social Services, and remote work jobs
are a column rather than a category. The rest of the map is just as hard to
guess — sales jobs data lives under Sales and under Account Management,
marketing jobs data under Advertising and Marketing, designer jobs data under
Design and UX, operations jobs data under Business Operations, HR jobs data
under Human Resources and Recruitment, finance jobs data under Accounting and
Finance, healthcare jobs data under Healthcare, and engineering jobs data under
Science and Engineering. Ask for a name the board does not use and you get nothing back rather
than a guess, and Unknown is one of the 32 real names, not a parsing failure.
Every run leaves a job listings dataset — JSON, CSV or Excel — with the job listings full text already in it, so the job posting text is in the same row as the title rather than behind a second call: the advert arrives inside the search response, so nothing is fetched twice.
How does this Actor get past the cap?
Why a Max jobs setting cannot help you
Raising a limit does not raise the ceiling. The ceiling belongs to the source: the public search endpoint refuses page 100 and pins the page size at 20, so a single query is worth 1,980 rows and no input value changes that. Ask one broad query for fifty thousand jobs and you still get 1,980, then silence. The only way through is to stop asking one broad question.
What the Actor does instead
Before paging, the Actor reads how many jobs the current query claims. If that number is under the cap, it simply pages the query out. If it is over, the query is split along the next unused axis — category first, then seniority, then location — and each child query is measured the same way. The walk repeats until every leaf fits, which is why one run can return far more than any single search could.
The values used for splitting are not a hard-coded list. The Actor samples the first three pages of the current query and collects the category, level and location names that the returned jobs actually carry. The Muse has no taxonomy endpoint to read, and a hand-written list would drift out of date the moment the source added a category.
What happens to jobs that appear in two slices
They are returned once. A job filed under both Data and Analytics and Software Engineering shows up in both slices, so the walk keeps the set of ids it has already yielded and drops the repeat before it reaches the dataset — which means before it reaches your bill. The measured run above ended with zero duplicates across 30 queries.
The walk also has a hard stop of 2,500 HTTP requests per run, so a pathological split cannot spin forever. When it trips, the run report says so instead of pretending the board was exhausted.
Fields
| Field | What it is |
|---|---|
title, company | headline and employer |
location, location_city, location_state, location_country | the label split up — Jacksonville, FL becomes city, state and United States |
is_remote | taken from the job's own location entry, never guessed from the text |
category, level, employment_type, tags | how The Muse files the job |
description_text, description_markdown, description_html | the full advert in three forms; pick one to keep rows small |
contact_emails, contact_urls | pulled out of the advert body |
published_at, posted_days_ago | when it appeared |
change_type | in incremental runs: NEW, UPDATED or UNCHANGED |
url | the listing page |
Empty means the source did not publish it. location_country stays empty rather
than being filled with a guess when the label carries only a city.
How the location label is taken apart
The Actor splits the label on commas and reads the tail: a two-letter code means
a US state, and the country is then set to United States; anything else is
treated as the country itself. So Jacksonville, FL yields a city, a state and
a country, while Munich, Germany yields a city and a country with
location_state left empty.
Nothing is inferred beyond that rule. A bare Flexible / Remote stays in
location as written, and locations_all keeps every place the employer
listed, not just the first.
Fields in detail
Three shapes of the same advert
The source ships the advert as HTML. The Actor keeps that original in
description_html, renders a readable plain-text version into
description_text, and converts headings, lists, links, bold and italic into
description_markdown. Markdown is usually the right choice for an LLM prompt;
plain text is the cheapest to store.
All three together roughly triple the weight of a row, which is why the Description format input defaults to plain text and drops the rest. If you need every form, choose All three deliberately rather than by accident.
Contacts out of the advert body
Some employers put a hiring manager's address or an application link straight
into the advert. The Actor scans the plain-text body and returns whatever it
finds in contact_emails and contact_urls, deduplicated and stripped of
trailing punctuation. Most jobs have neither; the Contacts dataset view exists
for the ones that do.
What is deliberately missing
There is no salary field. The Muse does not publish structured pay on the public
jobs endpoint, and a number guessed out of the advert text by a model is a
number you cannot use. Rather than ship a column that is wrong often enough to
matter, this Actor ships no column at all — if pay figures are in the body, the
body is right there in description_text.
Remote is a location, not a word
Remote only uses the job's own location entry. Searching the advert body for
"remote" instead would match "no remote work" just as happily —
tests/test_logic.py keeps a trap job for exactly that case.
Filters run before you are charged
Keywords, exclusions, country, category, seniority, remote, posting age, employer allow and block lists. A row a filter removes never reaches the dataset and never appears on the bill. Dataset views can only choose columns — no view on Apify can drop rows — so the filtering lives in the input, where it is also cheaper. See the dataset documentation for what a view can and cannot do.
The run report records how many rows each filter removed, so a surprisingly small result is explainable rather than mysterious.
Running it on a schedule
How do I pay only for what is new?
Turn on Only jobs not seen before and attach a
schedule. The Actor keeps the ids
and a short content fingerprint of everything it has delivered in a named
key-value store, which — unlike the default store — survives from one run to the
next. Tomorrow's run returns the jobs that appeared since today and marks them
NEW, and you are billed for those alone.
The memory holds 150,000 keys. A key weighs about 25 bytes, and one record in a key-value store is capped at 9 MB; 300,000 keys measured 8.0 MB, close enough to the ceiling that half of it is kept in reserve. When the memory fills, the oldest keys are dropped first, never random ones.
What counts as a change
The fingerprint covers the title, the employer and the first 2,000 characters of
the advert. A job whose text was rewritten comes back as UPDATED; a job that
merely sat there comes back as UNCHANGED and, by default, is not returned at
all — you already paid for it once. Switch on Also return unchanged jobs if
your pipeline would rather see the full picture every morning.
Putting the output somewhere useful
The dataset can be pushed to Google Sheets, Slack, a webhook or your own database through the platform's integrations and webhooks, or pulled directly from the Apify API. For agent pipelines, the store's MCP server can call this Actor as a tool — Compact output is there precisely for that.
Questions people ask
How many jobs can one run return?
There is no cap in the Actor itself. The measured run returned 2,060 unique jobs in 176 seconds, which is already above what a single query of the source can produce, and the walk keeps splitting as long as slices remain. What bounds a real run is your Max jobs value, your spending limit and the 2,500-request ceiling on the walk, in that order.
Why does the public API refuse page 100?
That is the source's rule, not a guess. Page 99 returns 20 jobs, page 100 returns HTTP 400, and the page size ignores whatever you pass — send fifty and twenty come back. Twenty rows times ninety-nine pages is the 1,980 ceiling every naive The Muse scraper runs into, including the ones that do not tell you.
Does it fetch the full advert text?
Yes, and without a second request. The Muse returns the advert body inside the search response itself, which is unusual and is the reason this Actor is priced below the rest of the category. Bodies of about 5,000 characters arrive with the listing, already converted to plain text and Markdown as well as the original HTML.
What does a first run cost?
Press Start with nothing filled in and the run stops after 300 jobs — about fifteen cents at $0.49 per 1,000. That default exists so an empty form is a trial and not an accidental order for four hundred thousand rows. Set Max jobs to 0 once you know what you want.
What happens if I set a spending limit?
The Actor reads the limit at start and refuses to return more rows than it covers. This matters because the platform stops charging when a limit is reached but does not stop the run, so an Actor that ignored the limit would keep scraping at the developer's expense and yours in compute. See pay-per-event pricing for how the charging model works.
How do I get only new jobs each morning?
Set your filters, switch on Only jobs not seen before, save the configuration as a task and give it a daily schedule. The first run fills the memory and bills for everything it returns; every run after that returns and bills for the difference. Leave Also return unchanged jobs off unless you need the full set each time.
Can I feed this straight into an LLM?
Turn on Compact output for 14 core fields with no advert body, or keep the body and set Description format to Markdown so headings and lists survive the trip into a prompt. Drop empty fields removes keys the employer never filled in, which shortens the JSON an agent has to read and cuts the token bill.
Why are some fields empty?
Because the employer left them empty at the source. Nothing here is back-filled
by inference: a job with no seniority stated returns an empty level, a
location with no country in it returns an empty location_country, and an
advert with no email in it returns an empty contact_emails. An empty cell is
information — a wrong cell is not.
Does it need a proxy?
There is no proxy input, and every number quoted on this page was measured without one. The client retries on failure, honours the source's Retry-After on HTTP 429, and applies that cooldown across all threads at once so a slowdown request is not undone by the rest of the run hammering the same host.
How do I filter by country?
Use Only these countries with the country as it appears after parsing —
United States, Germany. Because the country is derived from the location
label rather than a field the source publishes, a job whose label is only a city
has no country and will not pass a country filter. Location contains is the
looser tool for those cases.
Output views
The dataset arrives with four views, so the same run can be read four ways: the default Jobs table, Location for the parsed city, state, country and remote flag, Contacts for the addresses and links found in advert bodies, and All fields for everything including both extra description formats. A view selects columns only — it never removes rows, and it never changes the bill.
Honest limits
- No salary column, for the reason given above.
- Splitting is by category, seniority and location — those are the axes the source exposes on the search endpoint. A query that stays above the cap even after all three are used is reported as capped, with the number of jobs it could not reach, rather than quietly truncated.
- The category list in the input is a snapshot of what the source was serving when it was taken; The Muse publishes no endpoint that lists them.
posted_days_agois computed in UTC from the publication timestamp, so a job posted late in the day may read as one day old sooner than you expect.
Jobs this board does not carry
The Muse is a curated board: employers pay to be listed, so it is deliberately
narrower than the open market. Three sibling Actors cover the vacancies that
never reach it, and each stamps its rows with a source field so their datasets
can be concatenated and still be told apart:
- ATS Jobs Scraper — straight from the employer's own careers page across 20 applicant tracking systems, which is where a listed employer's remaining vacancies sit.
- Jobs.ch Scraper — Switzerland.
- SEEK, JobStreet & JobsDB Scraper — Australia, New Zealand and six Asian markets.
A run succeeded but the dataset is empty — is that a failure?
Not necessarily, and the Actor always says which case it is. The run report
carries empty_reason and the log carries a warning naming it:
nothing-changed (the incremental mode working as asked — everything had
already been delivered by an earlier run, so nothing was written and nothing
was charged), filtered-out (your filters removed everything that was
collected), source-returned-nothing (the search matched nothing at all), or
charge-limit-reached (the run's spending limit was already used up). A silent
empty dataset is treated as a defect, not as a normal outcome.
Reproducing the numbers
python3 tests/run_all.py --live
Every figure above comes from that run.