// freehire API

API reference

A read-first, open HTTP API over the freehire job catalogue — query jobs by rich filters, read companies, and (with a key) track applications. Base URL https://freehire.dev/api/v1.

Base URL

All endpoints are served under `https://freehire.dev/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 Authentication and API keys below.

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.

json
{ "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.

Search pagination is bounded: `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.

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” (API-key management, saved searches, subscriptions) accept only the cookie, so a leaked key cannot manage credentials. “Moderator” endpoints additionally require the moderator role.

Filtering jobs

These parameters apply to GET /jobs/search and GET /jobs/facets. Combine any of them with full-text q.

  • Repeat any facet param to OR its values: `skills=go&skills=rust` matches either.
  • Add `<param>_mode=and` to require all selected values: `skills=go&skills=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.

Facets

Every facet below supports repeat-OR, _mode=and, and _exclude as described above.

ParamFilterValues
collectionsCollectionyc, techstars, european, ai, mag7, bigtech, unicorn, fortune500, russian-roots
regionsRegionglobal, north_america, latam, eu, uk, mena, africa, apac, cis
work_modeWork formatremote, hybrid, onsite
categorySpecializationbackend, frontend, fullstack, mobile, devops, sre, data_engineering, data_science, data_analytics, ml_ai, qa, security, hardware, embedded, blockchain, design, product, project_management, management, marketing, sales, support, other
senioritySeniorityintern, junior, middle, senior, lead, staff, principal, c_level
skillsSkillsOpen vocabulary — call /jobs/facets for live values
domainsIndustryfintech, gambling, ecommerce, crypto, healthcare, saas, gamedev, edtech, adtech, govtech, media, travel, logistics, other
company_typeCompany typeproduct, startup, outsource, outstaff, agency, inhouse, government
countriesCountriesOpen vocabulary — call /jobs/facets for live values
relocationRelocationnot_supported, supported, required
employment_typeEmploymentfull_time, part_time, contract, internship
english_levelEnglishnone, a1, a2, b1, b2, c1, c2, native
posting_languageJob languageen, ru, uk
salary_currencyCurrencyUSD, EUR, GBP, RUB
company_slugCompanyOpen vocabulary — call /jobs/facets for live values
sourceSourcetelegram, workatastartup, remoteok, arc, arbeitnow, ashby, ashbygraphql, bamboohr, breezy, deel, eightfold, epam, freshteam, gem, getonbrd, globalpayments, greenhouse, gupy, himalayas, huntflow, icims, jazzhr, jibe, jobicy, jobstash, join, justjoin, lever, luxoft, mycareersfuture, oracle, personio, phenom, pinpoint, radancy, recruitee, remotive, rippling, smartrecruiters, successfactors, teamtailor, tecla, thehub, wantedkr, weworkremotely, workable, workday, workingnomads, wpyoast
company_sizeCompany size1-10, 11-50, 51-200, 201-500, 501-1000, 1000+
education_levelEducation levelnone, bachelor, master, phd
salary_periodSalary periodyear, month, day, hour

Numeric & boolean filters

ParamFilterValues
visa_sponsorshipVisa sponsorshiptrue, false
salary_minMinimum salaryinteger — jobs whose minimum salary is at least this (pair with salary_currency)
salary_maxMaximum salaryinteger — jobs whose maximum salary is at most this (pair with salary_currency)
experience_years_minMinimum experienceinteger — jobs requiring at least this many years

Recipes

Senior Go, remote, in the CIS region
https://freehire.dev/api/v1/jobs/search?q=go&seniority=senior&work_mode=remote&regions=cis
Backend roles, freshest first, in Germany
https://freehire.dev/api/v1/jobs/search?category=backend&countries=DE&sort=posted_at&order=desc
Must use both Go and Rust
https://freehire.dev/api/v1/jobs/search?skills=go&skills=rust&skills_mode=and
Exclude outstaff companies
https://freehire.dev/api/v1/jobs/search?company_type_exclude=outstaff
At least $100k, with visa sponsorship
https://freehire.dev/api/v1/jobs/search?salary_currency=USD&salary_min=100000&visa_sponsorship=true

Jobs

Public, unauthenticated reads. Jobs are returned in one wire shape (addressed by `public_slug`, never an internal id) shared by the list, detail, company, and search responses. Closed postings are excluded from lists and search and served only by the detail endpoint.

GET /jobs

Public

List jobs, newest first, with limit/offset pagination.

Query parameters

NameTypeReq.Description
limitintegerPage size, 1–100.(e.g. 20)
offsetintegerRows to skip.(e.g. 0)
curl
curl "https://freehire.dev/api/v1/jobs?limit=20&offset=0"
json
{
  "data": [
    {
      "public_slug": "senior-go-engineer-acme-1a2b",
      "title": "Senior Go Engineer",
      "company": "Acme",
      "company_slug": "acme",
      "url": "https://boards.greenhouse.io/acme/jobs/123",
      "location": "Remote — EU",
      "regions": ["europe"],
      "countries": ["DE"],
      "work_mode": "remote",
      "skills": ["go", "postgresql"],
      "collections": ["yc"],
      "source": "greenhouse",
      "posted_at": "2026-06-18T00:00:00Z",
      "enrichment": { "seniority": "senior", "category": "backend" }
    }
  ],
  "meta": { "total": 4213, "limit": 20, "offset": 0 }
}

GET /jobs/search

Public

Full-text + faceted search over open jobs.

Combine free-text `q` with any of the filter params below. Repeated facet params are ORed; add `<param>_mode=and` to require all, or `<param>_exclude=<value>` to exclude. Without `q`, results default to newest first; with `q`, to relevance.

Query parameters

NameTypeReq.Description
qstringFull-text query over title, company, and description.(e.g. golang)
sortstringOne of `created_at`, `posted_at`, `salary_min`, `salary_max`. Omit for relevance/newest.(e.g. posted_at)
orderstring`asc` or `desc` (default `desc`).(e.g. desc)
semantic_rationumberOpt-in hybrid search, 0–1 (default 0 = pure keyword). Needs the optional semantic index.(e.g. 0)
limitintegerPage size, 1–100.(e.g. 20)
offsetintegerRows to skip; `offset + limit` ≤ 10000.(e.g. 0)

Plus every filter in Filtering jobs.

curl
curl "https://freehire.dev/api/v1/jobs/search?q=golang&seniority=senior&work_mode=remote&regions=cis&sort=posted_at"
json
{
  "data": [ { "public_slug": "...", "title": "Senior Go Engineer", "...": "..." } ],
  "meta": { "total": 137, "limit": 20, "offset": 0 }
}

GET /jobs/facets

Public

Count of matching jobs per facet value (and numeric stats).

Takes the same `q` and filter params as search, but returns the distribution of values instead of a page of jobs — use it to build filter UIs or see how a filter narrows the set. Continuous numeric facets are returned as `stats` (min/max), not per-value buckets.

Query parameters

NameTypeReq.Description
qstringSame full-text query as search.(e.g. golang)
(any filter)stringAny search filter param narrows the counted set.(e.g. work_mode=remote)
curl
curl "https://freehire.dev/api/v1/jobs/facets?work_mode=remote"
json
{
  "data": {
    "total": 1820,
    "facets": {
      "seniority": { "senior": 640, "middle": 410, "junior": 120 },
      "category": { "backend": 700, "frontend": 380 }
    },
    "stats": {
      "salary_min": { "min": 20000, "max": 400000 }
    }
  }
}

GET /jobs/{slug}

Public

A single job by its public slug (serves closed jobs too).

Path parameters

NameTypeReq.Description
slugstringyesThe job `public_slug`.(e.g. senior-go-engineer-acme-1a2b)
curl
curl "https://freehire.dev/api/v1/jobs/senior-go-engineer-acme-1a2b"
json
{ "data": { "public_slug": "senior-go-engineer-acme-1a2b", "title": "Senior Go Engineer", "closed_at": null, "...": "..." } }

GET /jobs/{slug}/similar

Public

Jobs similar to the given one (semantic; may be empty).

Backed by the optional semantic index. Returns an empty list (not an error) when the source job is not indexed.

Path parameters

NameTypeReq.Description
slugstringyesThe job `public_slug`.(e.g. senior-go-engineer-acme-1a2b)

Query parameters

NameTypeReq.Description
limitintegerMax similar jobs.(e.g. 10)
curl
curl "https://freehire.dev/api/v1/jobs/senior-go-engineer-acme-1a2b/similar?limit=10"
json
{ "data": [ { "public_slug": "...", "title": "...", "...": "..." } ] }

Companies

Public reads. A company detail also returns a page of its open jobs.

GET /companies

Public

List companies with job counts; optional name filter.

Query parameters

NameTypeReq.Description
qstringCase-insensitive name substring filter.(e.g. acme)
limitintegerPage size, 1–100.(e.g. 20)
offsetintegerRows to skip.(e.g. 0)
curl
curl "https://freehire.dev/api/v1/companies?q=acme"
json
{ "data": [ { "name": "Acme", "slug": "acme", "job_count": 12 } ], "meta": { "total": 1, "limit": 20, "offset": 0 } }

GET /companies/{slug}

Public

A company and a page of its open jobs.

Path parameters

NameTypeReq.Description
slugstringyesThe company slug.(e.g. acme)

Query parameters

NameTypeReq.Description
limitintegerPage size for the jobs list.(e.g. 20)
offsetintegerRows to skip in the jobs list.(e.g. 0)
curl
curl "https://freehire.dev/api/v1/companies/acme"
json
{ "data": { "company": { "name": "Acme", "slug": "acme" }, "jobs": [ { "public_slug": "...", "...": "..." } ] } }

Authentication

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.

POST /auth/register

Public

Create an account and start a session.

Body

NameTypeReq.Description
emailstringyesAccount email (canonical key).(e.g. me@example.com)
passwordstringyesAccount password.
curl
curl -X POST "https://freehire.dev/api/v1/auth/register" \
  -H 'Content-Type: application/json' \
  -c cookies.txt \
  -d '{"email":"me@example.com","password":"hunter2hunter2"}'
json
{ "data": { "id": 1, "email": "me@example.com", "role": "user", "created_at": "2026-06-19T10:00:00Z" } }

POST /auth/login

Public

Sign in and start a session.

Body

NameTypeReq.Description
emailstringyesAccount email.(e.g. me@example.com)
passwordstringyesAccount password.
curl
curl -X POST "https://freehire.dev/api/v1/auth/login" \
  -H 'Content-Type: application/json' \
  -c cookies.txt \
  -d '{"email":"me@example.com","password":"hunter2hunter2"}'
json
{ "data": { "id": 1, "email": "me@example.com", "role": "user" } }

POST /auth/logout

Public

Clear the session cookie.

curl
curl -X POST "https://freehire.dev/api/v1/auth/logout" -b cookies.txt
json
{ "data": { "ok": true } }

GET /auth/me

Session or API key

The current user (cookie or API key).

curl
curl "https://freehire.dev/api/v1/auth/me" -H "Authorization: Bearer $FREEHIRE_API_KEY"
json
{ "data": { "id": 1, "email": "me@example.com", "role": "user" } }

GET /auth/oauth/providers

Public

List the enabled OAuth providers.

curl
curl "https://freehire.dev/api/v1/auth/oauth/providers"
json
{ "data": ["google", "github"] }

GET /auth/oauth/{provider}/start

Public

Begin the OAuth sign-in redirect.

Browser-only: redirects to the provider, then back to `/auth/oauth/{provider}/callback`, which sets the session cookie and redirects to the app. Not a JSON endpoint.

Path parameters

NameTypeReq.Description
providerstringyesOne of the enabled providers.(e.g. google)
curl
# open in a browser:
https://freehire.dev/api/v1/auth/oauth/google/start

API keys

Personal keys for non-browser access. Management is session-only (a leaked key cannot mint more keys). The plaintext token is shown exactly once, at creation — store it then.

POST /me/api-keys

Session only

Create a key; returns the plaintext token once.

Body

NameTypeReq.Description
namestringyesLabel to tell keys apart.(e.g. cli-laptop)
expires_atstring (RFC3339)Optional expiry; omit for no expiry.
curl
curl -X POST "https://freehire.dev/api/v1/me/api-keys" \
  -H 'Content-Type: application/json' \
  -b cookies.txt \
  -d '{"name":"cli-laptop"}'
json
{ "data": { "id": 7, "name": "cli-laptop", "token_prefix": "fh_ab12", "token": "fh_ab12...REDACTED...full-token-shown-once" } }

GET /me/api-keys

Session only

List your keys (metadata only, never the token).

curl
curl "https://freehire.dev/api/v1/me/api-keys" -b cookies.txt
json
{ "data": [ { "id": 7, "name": "cli-laptop", "token_prefix": "fh_ab12", "last_used_at": null, "expires_at": null } ] }

DELETE /me/api-keys/{id}

Session only

Revoke a key.

Path parameters

NameTypeReq.Description
idintegeryesThe key id.(e.g. 7)
curl
curl -X DELETE "https://freehire.dev/api/v1/me/api-keys/7" -b cookies.txt
json
{ "data": { "ok": true } }

Job interactions

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.

POST /jobs/{slug}/view

Session or API key

Record that you viewed the job.

Path parameters

NameTypeReq.Description
slugstringyesThe job `public_slug`.
curl
curl -X POST "https://freehire.dev/api/v1/jobs/<slug>/view" -H "Authorization: Bearer $FREEHIRE_API_KEY"
json
{ "data": { "job_id": 42, "viewed_at": "2026-06-19T10:00:00Z" } }

POST /jobs/{slug}/apply

Session or API key

Mark the job as applied to.

Path parameters

NameTypeReq.Description
slugstringyesThe job `public_slug`.
curl
curl -X POST "https://freehire.dev/api/v1/jobs/<slug>/apply" -H "Authorization: Bearer $FREEHIRE_API_KEY"
json
{ "data": { "job_id": 42, "applied_at": "2026-06-19T10:00:00Z" } }

POST /jobs/{slug}/save

Session or API key

Save (bookmark) the job.

Path parameters

NameTypeReq.Description
slugstringyesThe job `public_slug`.
curl
curl -X POST "https://freehire.dev/api/v1/jobs/<slug>/save" -H "Authorization: Bearer $FREEHIRE_API_KEY"
json
{ "data": { "job_id": 42, "saved_at": "2026-06-19T10:00:00Z" } }

DELETE /jobs/{slug}/save

Session or API key

Unsave the job (no-op if not saved).

Path parameters

NameTypeReq.Description
slugstringyesThe job `public_slug`.
curl
curl -X DELETE "https://freehire.dev/api/v1/jobs/<slug>/save" -H "Authorization: Bearer $FREEHIRE_API_KEY"
json
{ "data": { "job_id": 42, "saved_at": null } }

PATCH /jobs/{slug}/track

Session or API key

Set the application stage and/or notes.

A null field is left unchanged. `stage` is a controlled vocabulary: `applied`, `screening`, `responded`, `interview`, `offer`, `accepted`, `rejected`, `withdrawn` (an unknown value is a 400).

Path parameters

NameTypeReq.Description
slugstringyesThe job `public_slug`.

Body

NameTypeReq.Description
stagestringApplication stage from the vocabulary above.(e.g. interview)
notesstringFree-text notes.
curl
curl -X PATCH "https://freehire.dev/api/v1/jobs/<slug>/track" \
  -H "Authorization: Bearer $FREEHIRE_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"stage":"interview","notes":"call on Friday"}'
json
{ "data": { "job_id": 42, "stage": "interview", "notes": "call on Friday" } }

DELETE /jobs/{slug}/stage

Session or API key

Clear the application stage.

Path parameters

NameTypeReq.Description
slugstringyesThe job `public_slug`.
curl
curl -X DELETE "https://freehire.dev/api/v1/jobs/<slug>/stage" -H "Authorization: Bearer $FREEHIRE_API_KEY"
json
{ "data": { "job_id": 42, "stage": null } }

DELETE /jobs/{slug}/track

Session or API key

Remove the interaction record entirely.

Path parameters

NameTypeReq.Description
slugstringyesThe job `public_slug`.
curl
curl -X DELETE "https://freehire.dev/api/v1/jobs/<slug>/track" -H "Authorization: Bearer $FREEHIRE_API_KEY"
json
{ "data": { "ok": true } }

GET /me/jobs

Session or API key

Your tracked jobs joined with the job data.

Each item carries the job in the shared wire shape with your interaction timestamps alongside it. `meta.counts` gives the per-filter totals for tab badges. Closed jobs stay listed so your history never shrinks.

Query parameters

NameTypeReq.Description
filterstringSubset to return: `all`, `viewed`, `saved`, `applied`, or `board` (default `all`; an unknown value is a 400).(e.g. applied)
limitintegerPage size, 1–100.(e.g. 20)
offsetintegerRows to skip.(e.g. 0)
curl
curl "https://freehire.dev/api/v1/me/jobs?filter=applied" -H "Authorization: Bearer $FREEHIRE_API_KEY"
json
{
  "data": [
    {
      "job": { "public_slug": "senior-go-engineer-acme-1a2b", "title": "Senior Go Engineer", "...": "..." },
      "viewed_at": "2026-06-19T10:00:00Z",
      "saved_at": null,
      "applied_at": "2026-06-19T11:00:00Z",
      "stage": "interview",
      "notes": "call on Friday"
    }
  ],
  "meta": {
    "total": 5,
    "limit": 20,
    "offset": 0,
    "counts": { "all": 12, "viewed": 12, "saved": 3, "applied": 5, "board": 7 }
  }
}

GET /me/jobs/viewed

Session or API key

Slugs of jobs you have viewed.

curl
curl "https://freehire.dev/api/v1/me/jobs/viewed" -H "Authorization: Bearer $FREEHIRE_API_KEY"
json
{ "data": ["senior-go-engineer-acme-1a2b", "..."] }

Job submissions

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.

POST /submissions

Session or API key

Submit a vacancy for review.

Body

NameTypeReq.Description
urlstringyesLink to the original posting.(e.g. https://acme.com/careers/123)
titlestringyesJob title.(e.g. Senior Go Engineer)
companystringyesCompany name.(e.g. Acme)
locationstringFree-text location.(e.g. Remote — EU)
remotebooleanWhether the role is remote.(e.g. true)
descriptionstringJob description.
sourcestringOrigin hint (optional).
posted_atstring (RFC3339)Original posting date (optional).
curl
curl -X POST "https://freehire.dev/api/v1/submissions" \
  -H "Authorization: Bearer $FREEHIRE_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"url":"https://acme.com/careers/123","title":"Senior Go Engineer","company":"Acme","remote":true}'
json
{ "data": { "id": 9, "status": "pending", "title": "Senior Go Engineer", "company": "Acme", "url": "https://acme.com/careers/123" } }

GET /me/submissions

Session or API key

Your own submission queue.

curl
curl "https://freehire.dev/api/v1/me/submissions" -H "Authorization: Bearer $FREEHIRE_API_KEY"
json
{ "data": [ { "id": 9, "status": "pending", "title": "Senior Go Engineer" } ] }

GET /submissions

Moderator

The pending submission queue (moderators).

curl
curl "https://freehire.dev/api/v1/submissions" -H "Authorization: Bearer $MODERATOR_API_KEY"
json
{ "data": [ { "id": 9, "status": "pending", "submitter_email": "me@example.com" } ] }

POST /submissions/{id}/approve

Moderator

Approve a submission, minting a live job.

Path parameters

NameTypeReq.Description
idintegeryesThe submission id.(e.g. 9)
curl
curl -X POST "https://freehire.dev/api/v1/submissions/9/approve" -H "Authorization: Bearer $MODERATOR_API_KEY"
json
{ "data": { "id": 9, "status": "approved", "job_slug": "senior-go-engineer-acme-1a2b" } }

POST /submissions/{id}/reject

Moderator

Reject a submission with a reason.

Path parameters

NameTypeReq.Description
idintegeryesThe submission id.(e.g. 9)

Body

NameTypeReq.Description
reasonstringWhy it was rejected.(e.g. duplicate)
curl
curl -X POST "https://freehire.dev/api/v1/submissions/9/reject" \
  -H "Authorization: Bearer $MODERATOR_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"reason":"duplicate"}'
json
{ "data": { "id": 9, "status": "rejected", "review_reason": "duplicate" } }

Job reports

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

POST /jobs/{slug}/reports

Session or API key

Report a problem with a job.

Path parameters

NameTypeReq.Description
slugstringyesThe job `public_slug`.

Body

NameTypeReq.Description
reasonstringyesShort reason code/category.(e.g. expired)
detailsstringFree-text details.
contact_telegramstringOptional contact handle.
curl
curl -X POST "https://freehire.dev/api/v1/jobs/<slug>/reports" \
  -H "Authorization: Bearer $FREEHIRE_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"reason":"expired","details":"posting returns 404"}'
json
{ "data": { "id": 3, "status": "pending", "reason": "expired" } }

GET /reports

Moderator

The pending report queue (moderators).

curl
curl "https://freehire.dev/api/v1/reports" -H "Authorization: Bearer $MODERATOR_API_KEY"
json
{ "data": [ { "id": 3, "status": "pending", "job_slug": "...", "job_title": "..." } ] }

POST /reports/{id}/resolve

Moderator

Resolve a report, optionally closing the job.

Path parameters

NameTypeReq.Description
idintegeryesThe report id.(e.g. 3)

Body

NameTypeReq.Description
close_jobbooleanSoft-close the reported job.(e.g. true)
curl
curl -X POST "https://freehire.dev/api/v1/reports/3/resolve" \
  -H "Authorization: Bearer $MODERATOR_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"close_job":true}'
json
{ "data": { "id": 3, "status": "resolved" } }

POST /reports/{id}/dismiss

Moderator

Dismiss a report with a reason.

Path parameters

NameTypeReq.Description
idintegeryesThe report id.(e.g. 3)

Body

NameTypeReq.Description
reasonstringWhy it was dismissed.(e.g. not an issue)
curl
curl -X POST "https://freehire.dev/api/v1/reports/3/dismiss" \
  -H "Authorization: Bearer $MODERATOR_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"reason":"not an issue"}'
json
{ "data": { "id": 3, "status": "dismissed", "review_reason": "not an issue" } }

Moderator jobs

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.

POST /jobs

Moderator

Create a curated job.

Body

NameTypeReq.Description
urlstringyesLink to the posting.(e.g. https://acme.com/careers/123)
sourcestringSource label.(e.g. manual)
titlestringyesJob title.(e.g. Senior Go Engineer)
companystringyesCompany name.(e.g. Acme)
locationstringFree-text location.
remotebooleanWhether the role is remote.
descriptionstringJob description.
posted_atstring (RFC3339)Posting date.
curl
curl -X POST "https://freehire.dev/api/v1/jobs" \
  -H "Authorization: Bearer $MODERATOR_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"url":"https://acme.com/careers/123","title":"Senior Go Engineer","company":"Acme"}'
json
{ "data": { "public_slug": "senior-go-engineer-acme-1a2b", "title": "Senior Go Engineer", "manually_added": true } }

PATCH /jobs/{slug}

Moderator

Edit a curated job.

Path parameters

NameTypeReq.Description
slugstringyesThe job `public_slug`.

Body

NameTypeReq.Description
(any job field)variesSame fields as create; provided fields are updated.
curl
curl -X PATCH "https://freehire.dev/api/v1/jobs/<slug>" \
  -H "Authorization: Bearer $MODERATOR_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"title":"Staff Go Engineer"}'
json
{ "data": { "public_slug": "...", "title": "Staff Go Engineer" } }

Saved searches & subscriptions

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

GET /me/searches

Session only

List your saved searches.

curl
curl "https://freehire.dev/api/v1/me/searches" -b cookies.txt
json
{ "data": [ { "id": 2, "name": "Senior Go remote", "query": "q=go&seniority=senior&work_mode=remote" } ] }

POST /me/searches

Session only

Save a search.

Body

NameTypeReq.Description
namestringyesDisplay name.(e.g. Senior Go remote)
querystringyesCanonical filter query string.(e.g. q=go&seniority=senior&work_mode=remote)
curl
curl -X POST "https://freehire.dev/api/v1/me/searches" \
  -H 'Content-Type: application/json' -b cookies.txt \
  -d '{"name":"Senior Go remote","query":"q=go&seniority=senior&work_mode=remote"}'
json
{ "data": { "id": 2, "name": "Senior Go remote", "query": "q=go&seniority=senior&work_mode=remote" } }

PATCH /me/searches/{id}

Session only

Rename or re-query a saved search.

Path parameters

NameTypeReq.Description
idintegeryesThe saved-search id.(e.g. 2)

Body

NameTypeReq.Description
namestringNew name (optional).
querystringNew query (optional).
curl
curl -X PATCH "https://freehire.dev/api/v1/me/searches/2" \
  -H 'Content-Type: application/json' -b cookies.txt \
  -d '{"name":"Senior Go — EU remote"}'
json
{ "data": { "id": 2, "name": "Senior Go — EU remote", "query": "..." } }

DELETE /me/searches/{id}

Session only

Delete a saved search.

Path parameters

NameTypeReq.Description
idintegeryesThe saved-search id.(e.g. 2)
curl
curl -X DELETE "https://freehire.dev/api/v1/me/searches/2" -b cookies.txt
json
{ "data": { "ok": true } }

GET /me/subscriptions

Session only

List your subscriptions.

curl
curl "https://freehire.dev/api/v1/me/subscriptions" -b cookies.txt
json
{ "data": [ { "id": 1, "saved_search_id": 2, "channel": "telegram", "active": true } ] }

POST /me/subscriptions

Session only

Subscribe a saved search to a digest channel.

Body

NameTypeReq.Description
saved_search_idintegeryesThe saved search to subscribe.(e.g. 2)
channelstringyesDelivery channel.(e.g. telegram)
curl
curl -X POST "https://freehire.dev/api/v1/me/subscriptions" \
  -H 'Content-Type: application/json' -b cookies.txt \
  -d '{"saved_search_id":2,"channel":"telegram"}'
json
{ "data": { "id": 1, "saved_search_id": 2, "channel": "telegram", "active": true } }

PATCH /me/subscriptions/{id}

Session only

Pause or resume a subscription.

Path parameters

NameTypeReq.Description
idintegeryesThe subscription id.(e.g. 1)

Body

NameTypeReq.Description
activebooleanyesWhether the subscription is active.(e.g. false)
curl
curl -X PATCH "https://freehire.dev/api/v1/me/subscriptions/1" \
  -H 'Content-Type: application/json' -b cookies.txt \
  -d '{"active":false}'
json
{ "data": { "id": 1, "active": false } }

DELETE /me/subscriptions/{id}

Session only

Delete a subscription.

Path parameters

NameTypeReq.Description
idintegeryesThe subscription id.(e.g. 1)
curl
curl -X DELETE "https://freehire.dev/api/v1/me/subscriptions/1" -b cookies.txt
json
{ "data": { "ok": true } }

GET /me/telegram

Session only

Your Telegram link status (for digests).

curl
curl "https://freehire.dev/api/v1/me/telegram" -b cookies.txt
json
{ "data": { "enabled": true, "linked": true, "chat_id": 123456789 } }

POST /me/telegram/link

Session only

Start linking your Telegram account.

curl
curl -X POST "https://freehire.dev/api/v1/me/telegram/link" -b cookies.txt
json
{ "data": { "url": "https://t.me/free_hire_bot?start=..." } }

DELETE /me/telegram

Session only

Unlink your Telegram account.

curl
curl -X DELETE "https://freehire.dev/api/v1/me/telegram" -b cookies.txt
json
{ "data": { "ok": true } }