freehire
Applicationstrelov1/freehire
Open-source engine aggregating IT jobs directly from company career pages.
Overview
freehire crawls millions of live job postings straight from company ATS platforms like Workday, Greenhouse and Lever, normalizing and deduplicating them into one searchable catalogue. It offers faceted full-text search, saved-search digests, a CV builder, application tracking and LLM-powered fit analysis. Self-hostable with a Go backend, SvelteKit frontend, PostgreSQL and Meilisearch.
README Preview
\n\n\n \n \n\n\n# freehire\n\n### Every IT job, straight from the source.\n\n**3.3M+ live postings pulled directly from company career pages — no recruiters, no reposts, no dead links. Fully open source.**\n\n[**Try it live →**](https://freehire.me) · [Features](docs/features.md) · [Architecture](docs/architecture.md) · [Sources](#sources) · [API](#api) · [Add a source](#adding-a-source) · [Contributing](CONTRIBUTING.md)\n\n[](https://freehire.me)\n[](LICENSE)\n\n\n[](https://github.com/strelov1/freehire/stargazers)\n[](https://github.com/strelov1/freehire/graphs/contributors)\n[](https://discord.gg/79cgB9RRfx)\n\n\n\n\n\n \n \n \n \n\n\n\n\n\n\n\n\n\n## Why freehire?\n\n- **Straight from the source.** Every listing is crawled directly from a company's\n own ATS — Workday, Greenhouse, Lever, Ashby, iCIMS and a long tail of others — and\n links to the original posting. No recruiter reposts, no aggregator middlemen, no\n dead links.\n- **One schema, deduplicated.** The same role posted to three boards collapses into\n one entry: every posting is normalized into a single shape and deduplicated on a\n stable key.\n- **Search that understands jobs.** Faceted full-text search over region, work mode,\n seniority, skills and salary — derived from curated dictionaries, never guessed.\n- **Actually open.** MIT-licensed and self-hostable, pipeline and data both in the\n open. Adding a company is one row in the board catalog\n ([how](#adding-a-source)).\n- **Yours to build on.** A clean HTTP API, a CLI, Telegram digests, and a whole\n workspace on top of the catalogue — see [Beyond the catalogue](#beyond-the-catalogue).\n Use the hosted site, run your own, or build on top.\n\nAggregating **3.3M+ unique live postings** from **470,000+ companies** across\n**124 ATS platforms**, a long tail of aggregators and direct feeds, and **97\nTelegram channels** — **320 sources** in all, see [Sources](#sources) for the full\nbreakdown. Reposts of one job across boards ar
FAQ (3)
TroubleshootingWhy does `docker compose up` hang forever on the s3-init container after restarting the stack?
On the second make up, s3-init hangs because its retry loop only sends a bucket-creation PUT request. When the bucket already exists, s3mock responds with 409 BucketAlreadyOwnedByYou, curl -f exits with code 22, and the until loop retries forever, leaving the container stuck in Waiting status.
Fix: this was patched in PR #3265 (merged 2026-10-19). The init script now checks whether the bucket exists first and only creates it when missing, so it exits 0 on both first and subsequent runs (verified against adobe/s3mock:5.2.3).
To resolve, update your checkout and restart:
git pull origin main
make down
make upTroubleshootingHow to fix 'job not found' error that blocks swiping job cards?
The job usually wasn't deleted — the source renamed it, and a backfill re-minted its URL slug, so the swipe deck still held the old slug and POST /api/v1/jobs/<old-slug>/dismiss returned 404. Fix: update to the version containing PR #3289, which stops backfills from changing published job slugs and makes old slugs 301-redirect to the current URL. If cards still get stuck, patch web/src/lib/components/SwipeDeck.svelte: in judge(), the send.catch(...) handler restores the card to the top on any failure — change it to only restore the card on network errors, and discard it when the server responds with 404, since a 404 means the job is gone.
TroubleshootingWhy are countries and regions empty for Greenhouse job postings with a 'Locations:' prefix, and how to fix it?
Some Greenhouse postings store the location with a Locations: label prefix (e.g. Locations: San Francisco), which location.Parse cannot resolve, leaving jobs.countries and jobs.regions empty and causing country=us filters to miss the posting. Fix: strip the Locations: label in the Greenhouse adapter or in normalize.CleanLocation before parsing. Note that a listing-side fix does not repair already-stored rows — measure how many rows carry the prefix and re-derive them using the INGEST_REFETCH_ALL / backfill-derive jobs described in AGENTS.md.