Point your AI agent at freehire and let it find you a job.

Get the CLI →
v1.0.0
OpenAPI 3.1.0

freehire API — internal reference

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 endpoints that require the browser’s own session cookie — including moderator-only and browser-extension-only endpoints. None of them accept a personal API key, so an external client or agent cannot call them. For the endpoints a key (or no auth at all) can reach, see freehire.me/docs/api.

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,rust matches either. Repeating the param (skills=go&skills=rust) also works.
  • Add <param>_mode=and to require all selected values: skills=go,rust&skills_mode=and matches both.
  • Add <param>_exclude=<value> to exclude matches: company_type_exclude=outstaff drops 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, countries and cities widen each other instead of narrowing. regions=eu&countries=IT means "in Europe or in Italy", so it returns everything regions=eu alone 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 makes regions=eu&countries=BR ("Europe or Brazil") useful. There is no AND to switch on: _mode=and does not apply to geography.
  • Use regions=none to match jobs with no resolved geography (an empty region set); it ORs with real region values and supports _exclude like 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, with did_you_mean when 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/coverage and /companies, which answer {"data": ...} and grow a meta block only when there is something to warn about. /companies filters 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 regionq=go&seniority=senior&work_mode=remote&regions=cis
  • Backend roles, freshest first, in Germanycategory=backend&countries=DE&sort=posted_at&order=desc
  • One country only — no region param, or it widens back outcountries=IT&employment_type=contract
  • Must use both Go and Rustskills=go,rust&skills_mode=and
  • Exclude outstaff companiescompany_type_exclude=outstaff
  • At least $100k, with visa sponsorshipsalary_currency=USD&salary_min=100000&visa_sponsorship=true
  • Excluding jobs that need a security clearancerequires_clearance=false
  • Only jobs that need a security clearancerequires_clearance=true
Server:https://freehire.me/api/v1
No authentication selected
Client Libraries

Company feedback

Signed-in users leave a 1–5 star rating plus category and text about a company, shown under their site-wide pseudonymous persona (never a user id). Reads are public; writes are cookie-only. A reader can report a specific review, and a moderator can hide it.

Your own feedback on this company, across every category (empty if none).

Path Parameters
  • slug
    Type: string
    required

    The company slug.

Responses
  • application/json
Request Example for get/companies/{slug}/feedback/mine
curl https://freehire.me/api/v1/companies/acme/feedback/mine \
  --cookie 'hire_token=YOUR_SECRET_TOKEN'
{
  "data": [
    {
      "id": 5,
      "author": "quiet-falcon-42",
      "rating": 4,
      "feedback_type": "interview",
      "body": "...",
      "created_at": "...",
      "updated_at": "..."
    }
  ]
}

Create or overwrite your feedback in one category on a company.

One entry per (user, category): posting again in the same feedback_type overwrites your existing review rather than adding a second one. The response's nested company field is the freshly recomputed rating count/average, so you can update your own view of the company without a second fetch. 422 on an empty body, 429 past the daily review cap.

Path Parameters
  • slug
    Type: string
    required

    The company slug.

Body
required
application/json
  • body
    Type: string
    required

    Free-text review.

  • feedback_type
    Type: string
    required

    Review category, e.g. interview, culture, compensation.

  • rating
    Type: integer
    required

    1–5 stars.

Responses
  • application/json
Request Example for post/companies/{slug}/feedback
curl https://freehire.me/api/v1/companies/acme/feedback \
  --request POST \
  --header 'Content-Type: application/json' \
  --cookie 'hire_token=YOUR_SECRET_TOKEN' \
  --data '{
  "rating": "4",
  "feedback_type": "interview",
  "body": "Fast process, clear communication."
}'
{
  "data": {
    "id": 5,
    "author": "quiet-falcon-42",
    "rating": 4,
    "feedback_type": "interview",
    "body": "Fast process, clear communication.",
    "created_at": "2026-06-18T09:12:00Z",
    "updated_at": "2026-06-18T09:12:00Z",
    "company": {
      "feedback_count": 12,
      "feedback_rating_avg": 4.1
    }
  }
}

Delete your feedback in one category (no-op if absent).

Returns the company's freshly recomputed counters, the same cast/clear shape as the vote endpoints.

Path Parameters
  • slug
    Type: string
    required

    The company slug.

Query Parameters
  • feedback_type
    Type: string
    required

    Which category to delete.

Responses
  • application/json
Request Example for delete/companies/{slug}/feedback
curl 'https://freehire.me/api/v1/companies/acme/feedback?feedback_type=interview' \
  --request DELETE \
  --cookie 'hire_token=YOUR_SECRET_TOKEN'
{
  "data": {
    "feedback_count": 11,
    "feedback_rating_avg": 4
  }
}

Report a specific review.

A second report of the same review by you is a silent no-op.

Path Parameters
  • id
    Type: integer
    required

    The feedback entry id.

Body
required
application/json
  • reason
    Type: string
    required

    Report reason code.

Responses
  • application/json
Request Example for post/company-feedback/{id}/report
curl https://freehire.me/api/v1/company-feedback/5/report \
  --request POST \
  --header 'Content-Type: application/json' \
  --cookie 'hire_token=YOUR_SECRET_TOKEN' \
  --data '{
  "reason": "spam"
}'
(204 No Content)

Every review with at least one report, most-reported first.

Responses
  • application/json
Request Example for get/company-feedback/reported
curl https://freehire.me/api/v1/company-feedback/reported \
  --cookie 'hire_token=YOUR_SECRET_TOKEN'
{
  "data": [
    {
      "id": 5,
      "author": "quiet-falcon-42",
      "rating": 1,
      "feedback_type": "culture",
      "body": "...",
      "created_at": "...",
      "updated_at": "...",
      "company_slug": "acme",
      "report_count": 3,
      "report_reasons": [
        "spam",
        "off-topic"
      ]
    }
  ]
}

Hide a review, dropping it from the company's public list and average.

Idempotent. 404 for an unknown id.

Path Parameters
  • id
    Type: integer
    required

    The feedback entry id.

Responses
  • application/json
Request Example for post/company-feedback/{id}/hide
curl https://freehire.me/api/v1/company-feedback/5/hide \
  --request POST \
  --cookie 'hire_token=YOUR_SECRET_TOKEN'
(204 No Content)

Authentication (Collapsed)

Register/login set the session cookie and return the user. Logout clears it. me resolves the caller (cookie or API key). OAuth sign-in is a redirect flow. Credential endpoints are rate-limited.

Job interactions (Collapsed)

Per-user tracking, addressed by the job slug. All accept the session cookie or an API key and are idempotent. The response is the interaction record for that job.

Job interactions Operations

In-app assistant (Collapsed)

The same agent the web app and browser extension chat with, over HTTP: create a conversation, then post messages to it. All routes accept the session cookie or an API key (autopilot is cookie-only — see below) and act on a session you own; someone else’s session id answers 404, same as one that never existed. Turns run one at a time per session — a message sent while one is already running queues rather than running alongside it. preset selects which conversation this is: chat (default), profile (the experience interviewer), browse (a browsing session held from the extension’s side panel), interview (a mock-interview rehearsal) or debrief (after a real one) — the last two must name the vacancy via ?job=<slug>, an application you already hold. A tailor session cannot be created here; it is minted by the CV tailoring bootstrap, which knows the CV and vacancy to bind it to.

In-app assistant Operations

Job submissions (Collapsed)

Any signed-in user can submit a vacancy for moderation and read their own queue. The review actions are moderator-only; approval mints a live job.

Job reports (Collapsed)

Any signed-in user can flag a problem with a live vacancy. Review actions are moderator-only; resolving may soft-close the reported job.

Moderator jobs (Collapsed)

Hand-curate a vacancy directly (moderators only). Approved submissions go through the same minting path, so a curated job is indistinguishable from an approved one.

Moderator jobs Operations

Profile & résumé (Collapsed)

Your career profile and stored CV, session-only (a browser feature, like saved searches). The profile is a singleton keyed by your session — no id in the path. The verdict and ATS report are read-only sub-resources computed from the profile and CV; résumé storage degrades to 501 when object storage is unconfigured.

Screening answers (Collapsed)

The six candidate-stated facts that repeat across ATS application forms and no CV can supply. A singleton per user — no id in the path. PUT is a partial update: a field the body omits keeps its stored value.

Screening answers Operations

Onboarding survey (Collapsed)

What you told us about your search when you signed up: how far along it is, the single biggest thing in its way, and what you earn today. A singleton per user — no id in the path. These answers describe you to us and reach no employer and no job search; what you WANT to be paid is a screening answer (desired_salary_*), not one of these. PUT is a partial update: a field the body omits keeps its stored value, and there is no way to clear one back to unstated.

Onboarding survey Operations

Saved searches & subscriptions (Collapsed)

Browser conveniences, session-only. A saved search stores a canonical filter query string; a subscription turns one into a recurring digest (Telegram, email, or your own webhook). Each operation is owner-scoped — a non-owned id is a 404.

Job lists (Collapsed)

Named lists of specific jobs — independent of the single-flag "save" — that you can optionally publish read-only by slug (see the public read under "Activity & shared job lists"). Browser conveniences, session-only. Each operation is owner-scoped — a non-owned id is a 404.

Push notifications & alerts (Collapsed)

The mobile app’s device push tokens, and the in-app notification center — a durable, channel-independent record of every delivered digest, reminder and nudge. All cookie-only.

Account, plan & extension (Collapsed)

The rest of the account surface: the password, deleting the account, the plan and what it allows today, and the two endpoints the browser extension runs on. Password and deletion are session-only — an API key must not be able to change or destroy the credential it would outlive.

Votes, notifications & discussions (Collapsed)

The lighter per-user surfaces: a vote on a job or company, the account-level notification rule (gates the saved-job apply reminder and both lifecycle nudges), and the public discussion threads. Votes and notification settings take a key; posting to a thread is browser-owned.

Market pulse (Collapsed)

Your own profile skills’ demand trend, joined against the weekly market snapshot. Cookie-only, unlike the aggregate insights above — this reads your saved profile.

Market pulse Operations

Employee referrals (Collapsed)

Two sides of the same marketplace: an employee offers to refer into the company they work at (proof required, moderated), and a candidate asks one of that company's approved referrers for an intro. A candidate never sees who the referrer is, and a referrer sees the candidate's CV only through a short-lived view. Moderation of offers is moderator-gated.

Talent Network (Collapsed)

A public, anonymised catalogue of candidates who opted into being found. Membership is a single toggle on users, distinct from user_profiles; a member is addressed by a minted handle, never by their account id or username. Every string a public response carries is a value a dictionary resolved, a number or a date — never text copied from a CV — so no name, employer, institution or free-text field appears anywhere in it.

Talent Network Operations

CV builder & tailoring (Collapsed)

Your CVs, and the tailoring flow that reframes one toward a single vacancy. Authoring is browser-owned — create, update and delete are session-only — while reading a CV, patching it, and rendering the PDF also accept an API key, because that is the half a tailoring agent drives. Every CV is owner-scoped: an id you do not own is a 404, not a 403.

Photo (Collapsed)

The one image the CV templates that print a portrait compose in. Cookie-only throughout — the image is PII, so there is no key-authenticated or public path.

Experience bank (Collapsed)

The durable record a CV is built from: employments, and the evidence atoms under them. Every atom carries who asserted it — you, or a model that inferred it — and only your own assertions may be written into a CV. Reading accepts an API key; editing is session-only.

Tailor your CV for this role?

We couldn't check your fit for this role — add a CV to your profile to see it next time.

A new version of freehire is available