Every statistic Finjobsly publishes about its own job listings is a direct, reproducible count over its own database -- nothing about Finjobsly’s own data is estimated, modelled, or purchased from a third party. This page documents exactly how a listing enters that database, how it is classified, and how each published figure is calculated -- so every number on the Hiring Indexand elsewhere in Finjobsly’s research can be traced back to a specific, auditable rule. A small set of free, clearly labelled third-party benchmarks also appear as separate market context (see below); they are never blended with, or used to recalculate, a Finjobsly figure.
Most listings come from an automated scraper that runs against a per-company source list (scraping_sources, one row per company careers page). Each source is scraped by an adapter matched to how that company publishes its jobs: the Greenhouse and Lever adapters call those ATS platforms’s own public, documented, no-auth JSON APIs; Ashby and Workable use the equivalent public posting APIs for those platforms; a generic JSON fallback handles sources with a nonstandard API shape; and a generic HTML fallback (which respects robots.txt) reads a careers page directly, preferring embedded schema.org/JobPosting structured data before falling back to heuristic parsing.
A scheduled job checks every 15 minutes for sources that are due to be scraped again, based on each source’s own configured frequency (12 hours by default, tuned per source). Every scrape run is logged with its outcome and job counts, and a source’s listings are never marked expired as a side effect of that source’s site being temporarily unreachable.
Separately, employers can list a role directly through Finjobsly’s own job-posting flow rather than being scraped from an external source.
When a scrape finds a job, it is matched against existing rows first by that source’s own listing ID (source_id + external_id), and if no match is found, by an exact match on company name, title and location. A match updates the existing row rather than creating a duplicate.
To avoid unnecessary writes (and unnecessary re-classification), each normalized listing has a hash computed over the fields that matter for change detection; if a re-scrape produces the same hash as the stored row, nothing is written. Listings are never hard-deleted -- a listing the scraper stops finding on its source for three consecutive clean runs is soft-deleted (marked expired) rather than removed, so historical figures that already counted it stay accurate.
A listing counts as active if its status is “active” or “published” and it has not expired. Expiry is resolved in order: if the listing has an explicit expiry date, that date governs. Otherwise, it expires 30 days after the later of its posted date or the date it was last reconfirmed live during a scrape -- so a listing with an old posted date but a recent reconfirmation is still counted as active, not stale.
This is the one definition of “active” used everywhere on Finjobsly that counts jobs -- the Hiring Index, category pages, and city pages all filter against the same rule, so a total never disagrees with itself across pages.
Each active listing is checked against every fintech sub-vertical’s keyword rules (the same rules that power Finjobsly’s category pages, e.g. /fintech-jobs, /ai-finance-jobs), applied to the listing’s enriched title and description text. A listing can match more than one sub-vertical, or none -- so vertical counts do not necessarily sum to the total active count, and are not mutually exclusive.
For “most in-demand roles” and salary insights, each listing’s free-text title is mapped to one of a fixed set of canonical role buckets (for example Software Engineer, Product Manager, Compliance, Data Scientist, Fraud Analyst) using keyword matching against the title text.
A title that does not clearly match any bucket is excluded from these role-based statistics rather than being folded into a generic “other” count that would misrepresent demand for a specific role.
“Top hiring locations” groups listings by country, using each listing’s structured country code (mapped to a display name, e.g. GB to United Kingdom). Listings without a resolvable country code are not counted toward this statistic.
Salary insights use a similar country-level grouping, resolved from the listing’s country code and its raw location text together, so a salary figure is never reported at a finer geographic grain than the underlying data actually supports.
Requested skills come directly from each listing’s structured skills field, populated when the listing is first scraped and normalized. Skills are counted as they appear (case-insensitively, so “SQL” and “sql” count together), not re-interpreted or expanded into related terms.
Only listings with both a minimum and a maximum pay figure are eligible, and only when both figures fall within a plausible annual-pay range (between $5,000 and $2,000,000) -- this filters out data-entry errors and placeholder values without discarding genuinely low or high, but real, figures.
Each eligible listing contributes the midpoint of its own minimum and maximum. Listings are grouped by normalized role and country; the published figure for a group is the median (the middle value) of that group’s midpoints, not an average, so a small number of outlier listings cannot skew the headline number.
A role/country group is only published once it has at least 3 eligible listings (see Sample thresholds below).
Roles, skills and locations are only listed once they appear in at least 3 active listings -- below that, a count is more likely to reflect one employer’s wording than a real market pattern, so it is withheld rather than published as if it were representative.
Top hiring employers are the one exception: every employer with at least one active listing is eligible, because a company’s own listing count is an exact fact about that company, not an estimate that needs a minimum sample to be trustworthy.
Salary insights require at least 3 eligible listings per role/country group, per the calculation above.
Active-listing figures (the Hiring Index and everything derived from it) are recomputed daily: the current calendar month’s row is overwritten each day as listings change. Once a month ends, its row is locked and never written to again, so a past edition of the Hiring Index never changes retroactively.
Finjobsly also records one immutable snapshot per day, separate from the monthly row, so that once enough days of history have accumulated, real 7-day, 30-day and 3-month trend changes can be computed from actual stored history rather than a live-vs-live comparison. Finjobsly does not publish a trend figure before it has enough real history to support one -- see the Trend section on the Hiring Index page.
Alongside the Finjobsly-sourced statistics above, Finjobsly’s Shared Intelligence API surfaces a small set of free, licensed third-party benchmarks as separate market context -- never blended into, averaged with, or used to recalculate any Finjobsly figure. Each is refreshed on a fixed schedule (monthly, or weekly for GDELT) directly from the source’s own public API, cached, and labelled with its retrieval date and source URL:
Every third-party figure above is read from a cached, timestamped copy of what that source last published -- never fetched live on a page request, and never merged into a Finjobsly-sourced statistic. If you see a third-party figure and a Finjobsly figure side by side, they are always two distinct numbers from two distinct populations.
The Hiring Index applies every rule on this page to Finjobsly’s current listings.