freehire API
Base URL
All endpoints are served under https://freehire.me/api/v1. The API is read-first and open: the job, search, facet, and company endpoints need no authentication and may be called cross-origin.
Authenticated endpoints accept either the browser session cookie (set by sign-in, same-origin) or a personal API key sent as a Bearer token — see the authentication model below. Keys are created in your account, at freehire.me/my/api-keys, not through this API.
Response envelope
Single items are wrapped as { "data": ... }. Collections add pagination metadata: { "data": [...], "meta": { "total", "limit", "offset" } }. Errors are { "error": "message" } with a matching HTTP status.
{ "data": { "...": "single item" } }
{ "data": [ ... ], "meta": { "total": 4213, "limit": 20, "offset": 0 } }
{ "error": "job not found" }
Pagination
List and search endpoints page with limit (default 20, max 100) and offset (default 0). meta.total reports the total matching the current filters, so you can compute the number of pages.
Pagination is bounded on every list endpoint: offset + limit may not exceed 10000 (pagination too deep → 400). This is deep-paging protection, not a cap on the reported total — use filters to narrow rather than paging that far.
The bound is a refusal rather than a clamp, deliberately: a clamped page would answer 200 carrying rows you did not ask for, and a client walking pages would loop over the same page forever without being able to tell. A 400 means "there is no such page", which is the truth.
It applies whichever store answers the endpoint, and it is not the same thing as the rate limit. A rate limit bounds requests per minute; on an endpoint whose offset you choose, the work per request is also yours to choose — so a client inside its request budget can still be asking for far more work than the budget implies. Both bounds exist and neither substitutes for the other.
Errors
Errors use standard HTTP status codes: 400 (bad request / invalid value), 401 (missing or invalid credentials), 403 (authenticated but not allowed, e.g. a non-moderator), 404 (no such job, company, or owned resource), and 503 (search temporarily unavailable). The body is always { "error": ... }.
Authentication model
Browser clients authenticate with an HttpOnly session cookie set on sign-in (same-origin; the SPA cannot read it). Non-browser clients use a personal API key as Authorization: Bearer <token>.
Endpoints marked “Session or API key” accept either; endpoints marked “Session only” (saved searches, subscriptions) accept only the cookie, so a leaked key cannot manage credentials. “Moderator” endpoints additionally require the moderator role.
External vs internal endpoints
This reference covers every endpoint a client can call from outside the browser: public reads, and endpoints that also accept a personal API key. Endpoints that require the browser’s own session cookie — saved searches, notifications, moderator tools, the browser extension — are not reachable with a key alone and are documented separately at freehire.me/docs/api/internal.
What is not here
This reference covers every endpoint you can call. A handful are deliberately left out because calling them directly is meaningless: the Gmail and calendar consent redirects (/me/gmail/connect, /me/calendar/connect, and their callbacks), which only a browser can complete; the Telegram bot webhook; the browser-tool websocket relay; the sitemap-cursor helpers behind /sitemap.xml; and the /og/*.png social-preview cards, which render an image rather than answer with JSON.
Managing API keys (POST, GET and DELETE on /me/api-keys) is not documented here, because no API client can call it. Those endpoints accept only the browser session cookie, and additionally require a freshly proven identity — a password re-entry or a sign-in-provider check — that a script has no way to supply. Create and revoke keys in your account instead, at freehire.me/my/api-keys; the plaintext token is shown exactly once, at creation, so store it then.
The /jobs/{slug}/fit endpoints are pre-rename aliases of /jobs/{slug}/match-analysis and hit the same handlers. They still work, so existing clients do not break — use the match-analysis paths in new code.
Most of this API is also a CLI. If you are writing an agent rather than an integration, freehire covers the same surface with less ceremony — search, tracking, the inbox and CV tailoring — over one API key.
Filtering jobs
These parameters apply to GET /jobs/search and GET /jobs/facets. Combine any of them with full-text q.
- Pass multiple values as a comma-separated list to OR them:
skills=go,rustmatches either. Repeating the param (skills=go&skills=rust) also works. - Add
<param>_mode=andto require all selected values:skills=go,rust&skills_mode=andmatches both. - Add
<param>_exclude=<value>to exclude matches:company_type_exclude=outstaffdrops outstaff jobs. - Different facets are ANDed together; numeric and boolean filters are ANDed too. The geography facets are the one exception — see below.
- Geography is a single OR group:
regions,countriesandcitieswiden each other instead of narrowing.regions=eu&countries=ITmeans "in Europe or in Italy", so it returns everythingregions=eualone would. To search one country, drop the region:countries=IT. The three name a single concept — where — so picking two places reads as "either", which is what makesregions=eu&countries=BR("Europe or Brazil") useful. There is no AND to switch on:_mode=anddoes not apply to geography. - Use
regions=noneto match jobs with no resolved geography (an empty region set); it ORs with real region values and supports_excludelike any region. - A param no filter reads is ignored rather than refused — so old links and saved searches keep working — and comes back in
meta.ignored_params, withdid_you_meanwhen it is only the singular of a real facet. Check it: a dropped filter otherwise looks like a genuinely broad result. At most 10 are listed per response. The same report rides on/jobs/facets,/market/coverageand/companies, which answer{"data": ...}and grow ametablock only when there is something to warn about./companiesfilters on its own vocabulary, so a jobs facet sent there is reported as ignored.
Facets
Every facet below supports repeat-OR, _mode=and, and _exclude as described above.
| Param | Filter | Values |
|---|---|---|
collections |
Collection | yc, techstars, a16z-portfolio, a16z-speedrun, european, ai, mag7, bigtech, unicorn, fortune500, eastern-roots, indian-roots, ai-native, uk-skilled-worker-sponsor, nl-recognised-sponsor, us-h1b-sponsor |
regions |
Region | global, north_america, latam, eu, uk, mena, africa, apac, cis, none |
work_mode |
Work format | remote, hybrid, onsite |
category |
Specialization | software_engineering, backend, frontend, fullstack, mobile, devops, sre, network_engineering, data_engineering, data_science, data_analytics, ml_ai, ai_engineering, qa, security, hardware, embedded, blockchain, architecture, design, creative, engineering_design, industrial_engineering, product, project_management, management, healthcare, skilled_trades, retail, hospitality, logistics, education, personal_services, administration, marketing, sales, support, business_analysis, solutions_engineering, developer_relations, technical_writing, recruiting, hr, finance, legal, operations, customer_success, other |
ai_archetype |
AI Specialization | rag_app_builder, agent_builder, cloud_ml_platform_engineer, ml_trainer_researcher, fullstack_ai_engineer, devops_infra_engineer |
seniority |
Seniority | intern, junior, middle, senior, lead, staff, principal, c_level |
role_type |
Role type | people_manager — the title names a people-management role. One value only: carrying nothing means no management marker was found, NOT that the posting is individual-contributor work, so role_type_exclude means "no marker", not "IC" |
skills |
Skills | Open vocabulary — call /jobs/facets for live values |
domains |
Industry | fintech, crypto, ecommerce, gambling, gamedev, media, travel, healthcare, edtech, govtech, devtools, cybersecurity, ai, hrtech, adtech, proptech, logistics, mobility, climatetech, other |
company_type |
Company type | product, startup, outsource, outstaff, agency, inhouse, government |
countries |
Countries | Open vocabulary — call /jobs/facets for live values |
cities |
City | Open vocabulary — call /jobs/facets for live values |
relocation |
Relocation | not_supported, supported, required |
employment_type |
Employment | full_time, part_time, contract, internship, fellowship |
english_level |
English | none, a1, a2, b1, b2, c1, c2, native |
posting_language |
Job language | Open vocabulary — call /jobs/facets for live values |
reality |
Posting reality | fresh, stale, likely-evergreen |
salary_currency |
Currency | USD, EUR, GBP, RUB |
company_slug |
Company | Open vocabulary — call /jobs/facets for live values |
source |
Source | Open vocabulary — call /jobs/facets for live values |
company_size |
Company size | 1-10, 11-50, 51-200, 201-500, 501-1000, 1000+ |
education_level |
Education level | none, bachelor, master, phd |
salary_period |
Salary period | year, month, day, hour |
Numeric & boolean filters
| Param | Filter | Values |
|---|---|---|
visa_sponsorship |
Visa sponsorship | true, false |
requires_clearance |
Security clearance required | true, false |
salary_min |
Minimum salary | integer — jobs whose minimum salary is at least this (pair with salary_currency) |
salary_max |
Maximum salary | integer — jobs whose maximum salary is at most this (pair with salary_currency) |
experience_years_min |
Minimum experience | integer — jobs requiring at least this many years |
experience_years_max |
Maximum experience | integer — jobs requiring at most this many years, the same figure experience_years_min bounds from below. Use 0 for jobs stating no prior experience is required. Either bound excludes jobs that state no requirement at all |
posted_within_days |
Posted within | integer — jobs whose SOURCE states a posting date in the last N days. Some boards restate that date on every crawl, so a job open for months can satisfy a narrow bound here; open_within_days is the one that cannot be rewritten from outside |
open_within_days |
Open within | integer — jobs first recorded by freehire in the last N days, i.e. how long the posting has been in the catalogue regardless of the date its source states. Independent of posted_within_days; both may be set and they narrow together |
Recipes
- Senior Go, remote, in the CIS region —
q=go&seniority=senior&work_mode=remote®ions=cis - Backend roles, freshest first, in Germany —
category=backend&countries=DE&sort=posted_at&order=desc - One country only — no region param, or it widens back out —
countries=IT&employment_type=contract - Must use both Go and Rust —
skills=go,rust&skills_mode=and - Exclude outstaff companies —
company_type_exclude=outstaff - At least $100k, with visa sponsorship —
salary_currency=USD&salary_min=100000&visa_sponsorship=true - Excluding jobs that need a security clearance —
requires_clearance=false - Only jobs that need a security clearance —
requires_clearance=true