How Stellenly works

How every job on this site is found, cleaned, read, double-checked by AI and scored, with all the rules we use to filter out noise. Job counts are live; the other figures are updated with every daily build (last: 27 Oct 2026).

1,956open jobs
255company job boards read directly
84%straight from the employer
322postings checked by AI
162companies with a Hiring Score

1. Overview

Stellenly is a set of small, automated jobs that run on GitHub Actions. They need no server and keep no user accounts. Jobs are stored in a Postgres database (Supabase). The website itself is plain static HTML that is rebuilt every day.

  1. CollectRead public job APIs and employers' own job boards
  2. FilterKeep only English postings located in Germany (or remote)
  3. Clean & readStrip HTML and boilerplate; detect the German level, salary, visa support and seniority
  4. StoreMerge duplicates, count reposts, hide jobs that disappeared
  5. Validate with AIAn LLM re-reads each posting; its answer counts only with a matching quote
  6. CompareFind similar jobs with embeddings and turn the differences into hints
  7. Score & publishCalculate Hiring Scores, build pages, sitemap and feeds, deploy
WorkflowWhenWhat it does
Ingest jobsevery 6 hoursRuns the tests, then collects, filters, cleans and stores jobs and hides jobs that are gone
Job insightsafter every ingest + dailyEmbeddings, similar jobs, skills, hints, AI analysis and fact checks
Check dead linksdailyRe-opens up to 800 of the least recently checked postings and hides closed ones
Build site pagesdailyCompany pages, Hiring Scores, guides, city/field pages, sitemap, RSS
Deploy websiteafter each buildPublishes the static site on GitHub Pages (HTTPS, www.stellenly.com)
Migrate databaseon changeApplies versioned SQL migrations to the database
Guide review remindermonthlyFlags guides whose fact-check date is due

2. Where jobs come from

We only use public job feeds: no logins, no scraping behind bot protection. There are two kinds of source:

  • Arbeitnow: a public job API covering Germany and Europe (up to 40 pages per run).
  • Employers' own job boards, through the public APIs of their applicant-tracking systems: Greenhouse, Lever, Ashby, Personio, Recruitee, Workable and SmartRecruiters. This is the same data the company shows on its own careers page.

Automatic board discovery

Every Arbeitnow job has an "apply" link that redirects to the employer's tracking system. Once per company we follow that redirect, recognise the system and board name from the address (for example jobs.ashbyhq.com/<company> or <company>.jobs.personio.de), check that the board's public API answers, and save it in ats_boards.json. From then on that company is read directly. Companies without a recognised board are tried again after 30 days. Today 255 boards are read directly.

If the same job appears on both Arbeitnow and the employer's board, the employer's copy wins: its Apply button goes straight to the company.

SourceOpen jobs
Ashby678
Greenhouse434
Arbeitnow319
Lever214
Recruitee131
SmartRecruiters102
Workable51
Personio27

Being a polite client

  • Requests carry a clear User-Agent that links to the project.
  • Rate limits (HTTP 429) and server errors are retried with growing waits, following Retry-After.
  • There are pauses between pages, and very large SmartRecruiters boards are capped at 150 English jobs.
  • If a whole source returns zero jobs, the run still saves everything else, then fails loudly so we get an email.

3. Germany & English only

Language

The title and first 3,000 characters go through a language detector (with a stop-word count as backup). Anything not detected as English is dropped, so German-language postings never appear.

Location

The location text is checked in this order:

  1. German place: matches a list of German cities and federal states (with and without umlauts, e.g. München / Muenchen / Munich), "Germany/Deutschland", or a "DE" country code → keep.
  2. Clearly abroad: a known foreign city or country → drop.
  3. Generic ("Remote", "EMEA", "Europe") → keep only if the job is marked remote.
  4. Anything else is looked up once with OpenStreetMap's Nominatim geocoder and cached (328 places cached). If it's in Germany, keep; if it can't be placed, keep only remote roles.

The city filter on the search page only lists German cities, so foreign locations never show up in the dropdown.

Other things we drop

  • Postings without a title, company or link.
  • ATS test postings ("TEST – …").

4. Cleaning the text

  • HTML to text: tags are removed; paragraphs, line breaks and list items become clean lines and bullets; HTML entities (&amp;, &nbsp;…) are decoded; whitespace is collapsed; text is capped at 20,000 characters.
  • Boilerplate removal: aggregator footers such as "Find more English Speaking Jobs in Berlin on Arbeitnow" are removed.
  • Normalised names for matching: accents folded, gender suffixes like (m/w/d) removed, punctuation ignored.
  • City names are normalised ("Berlin Office", "Munich HQ", "Hybrid – Hamburg" → Berlin, Munich, Hamburg).

5. Reading each posting

Every posting is read by transparent rules first. AI only corrects these rules when it can prove it (see section 7).

German requirement, our main signal

The text is checked sentence by sentence, so "Fluent English required, German is a plus" counts as nice to have, not required. Each sentence that mentions German is classified:

ResultExample phrases
No German needed"no German required", "you don't need to speak German", "English only"
Nice to have"German is a plus", "basic German", "A2/B1", "willingness to learn German"
Required"fluent/business-fluent German", "German C1", "fluent English and German", "Deutschkenntnisse"
Not mentionedthe posting says nothing about German

False alarms are excluded: "strong German market", "German office", "German GAAP" or "German customers" are about Germany, not about the language. If sentences disagree, the strictest one wins (required > nice to have > not required).

Salary: only when the posting says it's pay

Showing a wrong salary is worse than showing none. So there are no estimates: a salary appears only if the posting states one.

  • An amount counts only if pay words are near it (salary, Gehalt, compensation, gross, per year, OTE…) and a currency is present.
  • Amounts next to funding, revenue, valuation, investors, budgets, perks, vouchers, training or equipment are ignored, unless the sentence clearly calls them salary. So "we raised €385 million" never becomes a €385k salary.
  • Millions/billions ("€280M", "2 Mio") are never salaries. Hourly pay is ignored.
  • Monthly pay is converted to yearly (×12). "60–80k" applies the k to both ends.
  • Plausibility: €15k–€300k per year, the top of a range at most 3× the bottom. A range beats a single figure.
  • Internships or student jobs above €60k are treated as parsing errors and shown as "no salary".

Right now 8% of jobs show a salary.

Visa, relocation, seniority and more

FieldRule
Visa sponsorship"visa sponsorship", "visa support", "Blue Card", "work permit support"… but not when the text says "no / cannot sponsor", "must already have a work permit" or "EU citizenship required"
Relocation"relocation package / support / budget", "we cover your relocation", "Umzugskosten"
SeniorityFrom the title (Intern/Werkstudent, Junior, Senior, Lead/Head/Principal/Director). Otherwise from the years of experience asked: ≤2 junior, ≤4 mid, more senior
ExperienceThe smallest "N+ years of experience" figure in the text (1–20)
Work setupHybrid ("3 days a week in the office"), Remote Germany, Remote EU ("anywhere in Europe") or on-site
Recruiting agencyCompany names like "Recruitment", "Staffing", "Personalvermittlung", known agencies, or "on behalf of our client". Marked with a badge; the "Direct employers only" filter hides them (10 right now)
Blue Card badgeThe stated salary meets the current EU Blue Card minimum (standard, or the reduced minimum for shortage jobs and recent graduates). This is about salary eligibility, not sponsorship
TagsField and tech keywords (Python, Product, Marketing…) plus the flags above, max 25 per job

6. Duplicates & dead jobs

  • One job, one entry: each job's ID is a fingerprint of title + company + city after normalisation, so the same job from two sources, or posted twice, is merged.
  • Reposts: if a job that was gone comes back, its repost counter goes up. Frequent reposting lowers the Hiring Score.
  • Gone from the source: after a complete read of a source, jobs that weren't seen any more are hidden (never deleted). Safety brake: if more than 60% of a source's jobs would disappear at once, nothing is hidden; a broken API must not empty the site.
  • Dead links: every day, postings that return 404/410, redirect to a generic careers or home page, or say "position has been filled" / "no longer available" are hidden.
  • Age limit: jobs posted more than 45 days ago are retired.

7. AI validation

Rules are fast and explainable, but they miss things. So a large language model (Google Gemini Flash, through an OpenAI-compatible API) re-reads postings. The AI never gets the last word without proof.

What the AI is asked

For each posting it returns JSON: a 2-sentence summary, the main tasks, up to 10 ranked skills, red flags, and the German requirement, salary, visa and relocation, each with the exact sentence it is based on.

How its answers are checked

  • Quote check: an answer is kept only if its quote really appears in the posting (ignoring case and punctuation). Invented text is rejected automatically.
  • German "not mentioned" is only accepted if the words German/Deutsch truly don't appear.
  • Salary needs a quote with a number and must pass the same plausibility limits as the rules (stricter for internships). If the AI says the posting states no salary, we never show one.
  • Visa / relocation "offered" needs a quote. "Not offered" is accepted, because rules can trip over things like "Visa card".
  • Checked facts are stored in llm_facts.json with a fingerprint of the posting text. If the employer edits the posting, it is checked again.

Salary second opinion

When a salary looks wrong compared with similar jobs (section 8), the AI is shown the salary sentences and asked whether our reading is right, monthly or yearly, and whether it includes bonus or equity. Its note appears next to the warning.

Running on a free tier

  • Up to 150 postings per run. Postings where the rules are least sure (German not mentioned, no salary) go first.
  • Calls are paced. Rate limits and overloads are retried with exponential backoff and jitter. A used-up daily quota stops cleanly, and an unavailable model falls back to another Flash model.
  • Answers wrapped in extra text or code fences are still parsed.

322 postings have AI-checked facts so far.

8. Similar jobs & hints

Each job is turned into a vector with a small open embedding model (bge-small-en-v1.5), weighted 60% title and 40% description so the role matters more than company boilerplate. Vectors are cached between runs.

  • Peers: the up to 30 most similar jobs at other companies with a cosine similarity of at least 0.75.
  • Salary peers: up to 15 similar jobs that list a salary (similarity ≥ 0.72), at the same seniority level when possible. With fewer than 8 we say there isn't enough data. Otherwise we show the 25th/50th/75th percentile.
  • Skills: matched against a curated list of 140 skills. Skills under "Requirements" weigh more than under "Nice to have" or "Benefits". We also show how often similar jobs ask for each skill, and which common skills this posting doesn't mention.

Hints shown on jobs

HintWhen
💶 Top 25% pay / Below-average paySalary above the 75th or below the 25th percentile of similar jobs
⚠ Unusually high / low payOver 1.8× or under 0.55× the median of similar jobs
⚠ Check salaryStudent job above €70k or junior job above €120k
Very wide rangeTop of the range more than 2.5× the bottom
⚠ Experience vs levelIntern/junior role asking for 5+ years
Asks more experience4+ years more than similar jobs usually ask
🇬🇧 Rare: no GermanNo German needed while at least half of similar jobs require it
🛂 Rare: visa supportVisa support while under 10% of similar jobs offer it
Posted in N citiesSame role listed in 5+ cities, often a talent pool or agency
⚠ Be carefulScam signals: apply via WhatsApp/Telegram, a free email address, application fees, crypto/forex trading, high pay with "no experience needed". A negation ("we will never ask for a fee") cancels the warning.

9. Stellenly Hiring Score

A 0–100 rating (100 points = 5 stars) of how a company hires, calculated only from its current postings on Stellenly. It is not based on employee reviews and says nothing about what it's like to work there. A company needs at least 3 open jobs to get a score. Scores are recalculated with every daily build.

FactorWeightFull points when…
English-friendly25%all jobs say German isn't needed or is only a plus. "Not mentioned" counts 60%
Salary transparency20%60% or more of the jobs show a salary
Visa & relocation15%50% or more mention visa sponsorship or relocation help
Hiring activity15%5 or more new jobs in the last 30 days
Stable postings15%no reposted jobs (0 points once half are reposts)
Fresh postings10%the median job is under 2 weeks old (0 points at 90 days)

Each factor scales linearly between 0 and its full-points level. The total is the weighted average. Grades: Excellent ≥ 85, Very good ≥ 70, Good ≥ 55, Fair ≥ 40, otherwise Limited.

10. Views & popularity

  • When someone opens a job's details or clicks Apply, the database adds 1 to that job's counter for the day. Nothing about the visitor is stored: no IP address, no cookie, no ID.
  • Each browser counts a job at most once per day (remembered locally in the browser), so refreshing doesn't inflate numbers.
  • Jobs show "N views · N applied". Most popular sorting uses the last 14 days, where an Apply click counts 3× an open.
  • Page visits are counted with GoatCounter, a privacy-friendly, cookieless analytics service.

11. Privacy

No accounts, no cookies and no tracking pixels. Saved and applied jobs stay in your own browser. See the privacy page.