Skip to content

Add FreeHire as a Find jobs source - #33

Closed
navaneeth-algorithm wants to merge 2 commits into
AienTech:mainfrom
navaneeth-algorithm:add-freehire-job-source
Closed

navaneeth-algorithm wants to merge 2 commits into
AienTech:mainfrom
navaneeth-algorithm:add-freehire-job-source

Conversation

@navaneeth-algorithm

@navaneeth-algorithm navaneeth-algorithm commented Oct 29, 2026 •

Copy link
Copy Markdown

Summary

  • Adds FreeHire (freehire.me) as a fourth Find jobs source — the open-source catalogue of IT postings crawled from company career pages (Greenhouse, Lever, Workday, …), with no recruiter reposts.
  • Same JobSourceAdapter pattern as JSearch / Adzuna / Arbeitsagentur: calls FreeHire’s public GET /api/v1/agent/jobs/search so results include full plain-text descriptions and need no API key.
  • Registers Prisma PostingSource.FREEHIRE, Settings → Job search choice, env (DOSSIER_JOBS_SOURCE=freehire, optional DOSSIER_FREEHIRE_URL for self-hosted FreeHire), attribution (“Jobs by FreeHire”), job agent support, unit/live tests, fixtures, and e2e fake routes.
  • Docs updated in README, PRIVACY, DEVELOPMENT, and .env.example.

Test plan

  • yarn vitest run src/lib/job-search/freehire.test.ts — 9/9 passed
  • Settings → Job search shows FreeHire (no key)
  • Find jobs search against FreeHire returns postings (e.g. India / Lead Software Engineer)
  • Optional live: JOBS_LIVE=freehire yarn vitest run src/lib/job-search/sources.live.test.ts
  • Job agent with FreeHire selected
  • Save / score / start application from a FreeHire posting

Screenshots of local verification are in the follow-up comment.

Wire FreeHire's public agent search API as a fourth JobSourceAdapter so Find jobs and the job agent can use ATS-direct postings without an API key.

Co-authored-by: Cursor <cursoragent@cursor.com>
@navaneeth-algorithm

Copy link
Copy Markdown
Author

Closing for now — opened before the author could run and verify locally. Will reopen (or open a new PR) once checked.

Temporary proof for review; can be dropped before merge if preferred.

Co-authored-by: Cursor <cursoragent@cursor.com>
@navaneeth-algorithm

Copy link
Copy Markdown
Author

Local verification

Verified on a local Docker Compose build of this branch (add-freehire-job-source).

Settings — FreeHire listed

Settings: FreeHire listed as a job source

Find jobs — FreeHire search results

Find jobs: FreeHire search results

Unit tests — freehire.test.ts 9/9 passed

Vitest: freehire.test.ts 9 passed

Happy to remove docs/pr-proof-freehire/ before merge if you prefer not to keep the screenshots in the tree.

@AienTech

Copy link
Copy Markdown
Owner

intresting! I'll look at it.

@AienTech

Copy link
Copy Markdown
Owner

Thanks a lot for this, @navaneeth-algorithm! FreeHire is a really good fit. I've taken it into Dossier with you as co-author, and it ships in 1.10.0, with your name mentioned in the release notes.

A few things changed on the way in, mostly from trying the adapter against the live API:

  • Posted within: the search didn't send a date, so "Past week" also returned postings from 2018 and 2020. It now sends posted_within_days.
  • Place and radius: a place in q only puts postings there first: "product designer in Hamburg" finds 11,858 postings, against 11,859 for all of Germany. I kept the place in q, because cities only knows FreeHire's own spelling of a city, and together with countries it widens to the whole country. Instead, the Within field is off while FreeHire is chosen, and the search summaries and the "nothing found" message leave the km out for it.
  • Paging: FreeHire answers nothing past the first 10,000 results of a search, so "Load more" stops there instead of showing an error.
  • Missing descriptions: a posting that comes without its text gets it from its page, the way Adzuna's postings do.
  • The label: "Jobs by FreeHire" is plain text, like the Bundesagentur's. The link is only for sources whose terms ask for one, and FreeHire's don't.
  • Order: FreeHire comes before the unofficial Bundesagentur für Arbeit in Settings.
  • DOSSIER_FREEHIRE_URL: compose.production.yml passes it on to the container now, Settings says which FreeHire Dossier searches instead of showing the variable's name, and the live test reads it (or .env) instead of a separate test variable.
  • Tests: two of them still expected the old texts (the Settings source list and the agent's message for the Bundesagentur), so they're updated.
  • The wiki: "Choosing a job source" gets a FreeHire section, including that FreeHire's terms ask for an API plan when you host Dossier for other people.

I left the screenshots out of the repo, but they helped a lot while reviewing. I'll close this PR in favour of the upcoming 1.10.0. Thanks again!

@AienTech AienTech closed this Oct 29, 2026
@navaneeth-algorithm

navaneeth-algorithm commented Oct 29, 2026 •

Copy link
Copy Markdown
Author

Thanks @AienTech ! Glad FreeHire fits. Appreciate you taking it in with the live-API fixes (posted_within_days, radius, paging, etc.) and the co-author credit in 1.10.0.

Happy for you to close this in favour of the release.

AienTech added a commit that referenced this pull request Oct 29, 2026
FreeHire as a fourth job source, free and without a key, a line about the company or role under each job on your resumes, and fixes for importing a resume with Anthropic, for DeepSeek's answers getting cut off, for JSearch searches that took too long, and for servers without a time zone.

## What's new

- **FreeHire as a job source (#33):** choose FreeHire in Settings → Job search to search its open catalogue: millions of postings read straight from companies' own career pages (Greenhouse, Lever, Workday and many more), in most countries, each with its whole text. It needs no key and no account, and the job agent can search it too. It has no distance filter: the place you enter puts postings there first, so **Within** is off while FreeHire is chosen. If you run your own FreeHire, `DOSSIER_FREEHIRE_URL` points Dossier at it. FreeHire's free use is for your own job search; a Dossier you host for other people needs one of [its API plans](https://freehire.me/terms). Thanks to @navaneeth-algorithm, who sent it.
- **A line about the company or role (#15):** each role in Profile → Experience has **About the company or role**, for a line like "Series B fintech, 40 people" that would otherwise sit in the first bullet, where it reads as something you did. Resumes show it under the role's title, in italics, smaller and muted, in every theme. Tailoring never rewrites it, the AI reads it as background about the employer, and importing a resume reads it when the resume has such a line. Thanks to @VictoriaDom for asking for it.

## Fixes

- **Importing a resume with Anthropic (#24):** with Anthropic chosen, it always failed with "The AI provider ran into a problem": Anthropic refuses the shape of answer Dossier asks for as too complex, and trying again sent the same. Dossier now asks once more with that shape in the instructions instead, and remembers to for that model. Thanks to @berkayturk, who sent the fix (#16), and to @ruchindwd for reporting it.
- **DeepSeek's answers cut off (#34):** with DeepSeek's API as the OpenAI-compatible server, creating an application failed with "The model's answer was cut off before it was finished": DeepSeek's models think before they answer unless told not to, and the thinking used up the room left for the answer. Dossier now tells DeepSeek's API (any address at deepseek.com) to answer without thinking first. Other servers get the same requests as before. Thanks to @raghav-sec for reporting it.
- **"JSearch isn't answering right now" (#18):** JSearch searches Google for Jobs while it answers, which can take longer than the 10 seconds Dossier waited, outside the US especially, so Find jobs, Test connection and the job agent gave up. JSearch now gets 45 seconds; the other sources keep 10. Thanks to @berkayturk, who sent the fix.
- **"Invalid time zone specified: Etc/Unknown" (#24):** without `TZ` in `.env`, `compose.production.yml` gave the container an empty time zone, and the job agent's schedule, the Google sync and Settings failed with that error until a browser had told Dossier its own. `TZ` now defaults to UTC, and Dossier uses UTC whenever the server's time zone isn't one it knows. Thanks to @ruchindwd for reporting it.
- **`DOSSIER_JOBS_ARBEITSAGENTUR=off` with Compose:** `compose.production.yml` didn't pass it on, so the Bundesagentur für Arbeit stayed on offer. It does now, and passes on the new `DOSSIER_FREEHIRE_URL` too.
- **Buttons named by what they show:** for voice control, the theme thumbnails, an application's **Theme** menu and the next interview on the dashboard are now named by the words they show.
- **Updates:** Next.js 16.4, the Anthropic SDK 1.3 (Dossier still sends the key as `X-Api-Key`, for gateways that only read that), Ollama 0.40.10 in the compose file, and others.

## Upgrading

Take a backup, then deploy `ghcr.io/aientech/dossier:v1.10.0` (see [Upgrading](https://github.com/AienTech/dossier/wiki/Upgrading) in the wiki). If you use `compose.production.yml`, take the new one: it defaults `TZ` to UTC and passes on `DOSSIER_FREEHIRE_URL` and `DOSSIER_JOBS_ARBEITSAGENTUR`. On its first start, Dossier adds the line about the company or role to each role, empty, and FreeHire to the job sources; nothing else changes in your data or configuration.

Exports now include the line about the company or role, so a 1.10.0 export can't be imported into an older version. Exports from older versions import as before.
@AienTech

Copy link
Copy Markdown
Owner

1.10.0 is out, with FreeHire in it. Thanks again, @navaneeth-algorithm!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants