Freshteam Jobs API
Query jobs from Freshteam boards through one API with ?source=freshteam — plus how Freshteam's job pages really behave, and why the platform is going away.
Freshteam is Freshworks' HR and applicant tracking product for small businesses,
with boards at <tenant>.freshteam.com/jobs. Its tenancy is India-heavy, then
US, UK and APAC.
Before anything else on this page: Freshworks is sunsetting Freshteam.
The platform is being shut down#
Renewals stopped on 2026-03-07. Existing customers keep access only to the end of their current term, which means the tenant pool doesn't drop off a cliff — it bleeds out through 2026 and 2027, one expiring contract at a time.
A page selling you a jobs API for a dying platform would be a bad page. So, the measurement rather than the pitch. As of 2026-07-15, of 1,605 known tenants:
| Tenants | |
|---|---|
| Serving at least one live job | 710 (~14,384 jobs) |
| Dead | 567 |
What that means if you're deciding whether to build this
Freshteam is still alive today and it is not alive long-term. Both halves are true and neither is the interesting one on its own.
If you need Freshteam data now, it's there and it's free to fetch. If you're costing a scraper as an asset you'll amortise over three years, don't: the asset expires. We built the adapter because it's cheap reuse of a JSON-LD path we already had, not because we think this platform has a future. Every number on this page needs re-measuring before anyone spends anything more on it, and when the live count collapses the right move is to drop the boards, not the code.
That framing also corrects our own earlier call. Freshteam was written off entirely on the sunset announcement, on the reasoning that a dying platform isn't worth an adapter. That was wrong in a specific way: "announced EOL" and "not serving jobs" are different facts, and only one of them had been checked. 710 live tenants is not nothing.
What the adapter reads#
# List — 1,000 postings per page
curl -L "https://<tenant>.freshteam.com/jobs?page=1"
# Detail — one JobPosting JSON-LD per posting
curl -L "https://<tenant>.freshteam.com/jobs/<id>/<slug>"The official /api/job_postings and /api/v1/job_postings both 401. The
public HTML is the only way in, and the body is detail-only, so it's an N+1.
Pagination is real here, unlike several platforms in this pool that ignore the
param and hand you page one forever: ?page=2 returns 1,000 ids with zero
overlap with page one. Crawled abnhire to exhaustion — 1,000/page × 5 pages =
5,000 distinct, page 6 adds nothing. But ?offset= and ?per_page= are
ignored (both return page one at 200), so page is the only lever, and the
termination condition is "a page contributed nothing new" rather than a total,
because no total is exposed.
What bites you#
A dead tenant answers 200, not 404#
Freshworks serves an ~889-byte "We couldn't find {domain}" stub for any
expired or unclaimed subdomain. At HTTP 200.
With the sunset actively killing tenants this is the common case, not an edge case — 567 of 1,605. And the failure is nasty in a specific way: treated as an empty board, a dead tenant expires every job you had for that company, and burns your whole detail budget on nothing. "Gone" and "hiring nothing" have to be told apart explicitly, and the only way to do it is to recognize the stub.
addressLocality and addressRegion are inverted platform-wide#
Freshteam puts the city in addressRegion and the state in
addressLocality — the exact opposite of the schema.org spec it's otherwise
following. Verified across 371 postings on ~200 tenants:
| Posting | addressLocality | addressRegion |
|---|---|---|
| Noida | Uttar Pradesh | Noida |
| San Diego | California | San Diego |
| Stuttgart | Baden-Württemberg | Stuttgart |
Why this one is worse than a normal bug
A correct schema.org reader gets this wrong, and gets it wrong silently: it
emits "State, City, Country", a downstream parser takes the first part as the
city, and every Freshteam posting in your database says its city is
California or Uttar Pradesh.
There's no error, no null, no anomaly — just a plausible city name in the city field on 100% of rows. A shared, correct, well-tested helper is the wrong tool here precisely because it's correct. Freshteam needs its own reader.
JSON-LD string values are entity-encoded, including the location#
' in a city name is not cosmetic. "Thompson's Station" contains a
semicolon, a location parser reads ; as a separator, and one city becomes
two junk cities: ["Thompson'", "s Station"]. Address parts have to be
entity-decoded before they're joined, not after.
About 8% of live tenants ship no JSON-LD at all#
5 of 60 tenants sampled (avomind, 219 jobs; oml; 10times; paxcom;
openinapp) render the body in JavaScript and carry no description anywhere in
the HTML. There's no fallback to reach for. Those postings are skipped rather
than stored empty — a bodyless job is not a job.
Two smaller ones#
Job ids are base64url, not numeric (JLX_yOg0d7bW). The obvious
/jobs/(\d+) regex matches nothing and the board reads as empty at HTTP 200.
remote="false" is an unchecked default, not an assertion. Freshteam's form
has no hybrid concept at all, so false lumps on-site and hybrid together — and
it's demonstrably stale on real postings (one is remote="false" with "Mode of
Work - Work from Home" in its body). Only "true" is trusted. Reading false as
On-site would mislabel every hybrid job on the platform.
What Freshteam simply doesn't have#
Verified across 371 postings, so you don't go looking: baseSalary 0/371.
validThrough 0/371. hiringOrganization.sameAs and .logo 0/371 —
only name. No comp, no expiry, and no org-domain signal, which makes attributing
a Freshteam posting to a real company genuinely hard rather than merely fiddly.
Where this stands#
?source=freshteam is served today. And the unusual thing to say at the bottom
of a page like this: it may not stay that way, and that would be the right
outcome. Freshteam is a shrinking pool with a known end date. When the live
tenant count collapses far enough, covering it stops being worth the request
budget, and the right move is the one in the warning above — drop the boards,
keep the code. We'd rather tell you that here than have you plan around a source
that quietly empties out.
Which is the argument for coding against the keys of
/v1/meta.sources rather than against this page:
they're the values ?source= actually accepts, with counts, so a source that
thins out shows you the count before it shows you an outage. Sources &
coverage says the same in prose.
See all platforms · quickstart.
Every source above normalises onto the same schema, with the hiring company already joined onto each posting.
Get an API key