Techmap (jobdatafeeds.com) sells high-volume job feeds: an API priced per thousand jobs, daily country files and a /count endpoint to estimate cost. BetterJobs replaces the feed-and-dedup work with one search that merges Techmap and up to six other sources into canonical jobs.
Techmap is one of the six providers behind BetterJobs, so its postings can still reach you through the waterfall.
High-volume job feeds from ATSs, job boards and public employment offices (jobdatafeeds.com).
Sources 200+ sources incl. 120 ATSs and 28 public employment offices New postings About 8M per month History 451M+ postings since 2020 Countries Claims 250 countries (about 125 with more than 100 jobs per month) Feeds Daily country feeds via AWS Data Exchange API $1 per 1k jobs; /count endpoint estimates cost Growth includes 2 partner providers; Pro and above include all six. GET /v1/providers shows what your plan enables.
Facts per Techmap (jobdatafeeds.com).
Provider docs ↗
Topic
Techmap (per Techmap)
BetterJobs
Delivery
API, plus daily country feeds via AWS Data Exchange
API: sync search (100 jobs per page) and async search (up to 10,000 jobs)
Estimate before you pay
/count endpoint
dry_run: true, free
Price
$1 per 1,000 jobs via the API
1 credit per unique job; dollar value depends on your plan (below)
Duplicates
isDuplicate flag on records
Merged into one canonical job; every source in sources[]; duplicates free
Formats
json, csv, rss, parquet
JSON API. CSV on Growth and above. No parquet or RSS in v1 preview
Raw posting markup
jsonLD (schema.org JobPosting)
Normalized fields only. No JSON-LD passthrough
History
451M+ postings since 2020
posted_within_days up to 365, include_closed: true for closed jobs
Honest price check
Per Techmap, its API costs $1 per 1,000 jobs. A BetterJobs credit costs more:
Growth $4.90, Pro $3.32, Scale $2.40 per 1,000 credits.
You pay the difference for merge and dedup across every source your plan enables (Growth: the BetterJobs index plus 2 partner providers; Pro and above: all six), cross-source p_real, lifecycle events and one bill. If you only need Techmap’s raw feed at volume, Techmap direct is cheaper. On Scale you can bring your own provider keys.
Before: pull a feed or API page, estimate with /count, drop flagged duplicates, normalize. techmap_count and techmap_fetch stand for your existing wrappers.
# Before (Techmap): estimate, fetch, then dedup and normalize yourself
cost = techmap_count ( query ) # /count estimates cost, per Techmap
rows = techmap_fetch ( query ) # $1 per 1k jobs, per Techmap
rows = [ r for r in rows if not r[ " isDuplicate " ] ]
jobs = [ normalize ( r [ " jsonLD " ]) for r in rows ] # your own mapping from schema.org JobPosting
After: estimate free, then fetch merged canonical jobs with a credit cap.
# Free estimate (replaces /count)
curl https://api.betterjobs.cc/v1/jobs/search \
-H " Authorization: Bearer $BETTERJOBS_API_KEY " \
-H " BetterJobs-Version: 2026-10-01 " \
-H " Content-Type: application/json " \
"title_or": ["Warehouse Manager", "Logistics Manager"],
"country_code_or": ["DE"],
"employment_type_or": ["full_time"],
"waterfall": { "strategy": "max_coverage" },
# Fetch merged jobs (replaces fetch + isDuplicate filter + normalize)
curl https://api.betterjobs.cc/v1/jobs/search \
-H " Authorization: Bearer $BETTERJOBS_API_KEY " \
-H " BetterJobs-Version: 2026-10-01 " \
-H " Content-Type: application/json " \
"title_or": ["Warehouse Manager", "Logistics Manager"],
"country_code_or": ["DE"],
"employment_type_or": ["full_time"],
"waterfall": { "strategy": "max_coverage", "max_credits": 100 },
API = " https://api.betterjobs.cc/v1 "
" Authorization " : f "Bearer { os.environ[ ' BETTERJOBS_API_KEY ' ] } " ,
" BetterJobs-Version " : " 2026-10-01 " ,
" title_or " : [ " Warehouse Manager " , " Logistics Manager " ] ,
" country_code_or " : [ " DE " ] ,
" employment_type_or " : [ " full_time " ] ,
" waterfall " : { " strategy " : " max_coverage " },
estimate = requests. post ( f " {API} /jobs/search" , headers = HEADERS , json = { ** BODY, " dry_run " : True } , timeout = 30 )
estimate. raise_for_status ()
print ( estimate. json () [ " estimate " ] ) # free, replaces /count
body = { ** BODY , " waterfall " : { ** BODY [ " waterfall " ], " max_credits " : 100 }}
resp = requests. post ( f " {API} /jobs/search" , headers = HEADERS , json = body , timeout = 30 )
jobs = resp. json () [ " data " ] # merged, deduplicated, normalized
const API = ' https://api.betterjobs.cc/v1 ' ;
Authorization: ` Bearer ${ process . env . BETTERJOBS_API_KEY } ` ,
' BetterJobs-Version ' : ' 2026-10-01 ' ,
' Content-Type ' : ' application/json ' ,
title_or: [ ' Warehouse Manager ' , ' Logistics Manager ' ] ,
employment_type_or: [ ' full_time ' ] ,
waterfall: { strategy: ' max_coverage ' },
async function search ( payload : object ) {
const res = await fetch ( ` ${ API } /jobs/search ` , { method: ' POST ' , headers , body: JSON . stringify (payload) } );
if ( ! res . ok ) throw new Error ( ` ${ res . status } ${ await res . text () } ` );
console . log (( await search ({ ... body, dry_run: true })) . estimate ); // free, replaces /count
const { data : jobs } = await search ( { ... body , waterfall: { ... body . waterfall , max_credits: 100 } } );
A daily country file becomes a daily search per country with one day of overlap:
"filters" : { "country_code_or" : [ " DE " ], "posted_within_days" : 2 },
"waterfall" : { "strategy" : " max_coverage " , "max_credits" : 10000 },
Send it to POST /v1/searches (async, up to 10,000 jobs), read the pages for free and upsert on id. Jobs from the overlap day that you already paid for are free. Add a search watch with job.closed to mark expired jobs, also free. The full recipe is on Sync patterns .
Size it before you schedule it
A whole country per day can exceed 10,000 jobs. Send the same filters and waterfall to POST /v1/jobs/search with "limit": 100 and "dry_run": true first (async searches have no dry_run). expected_unique_jobs_range counts every matching job, not one page. If its max is above 10,000, split by job_family_or or by title groups.
Field names in Techmap records and their BetterJobs equivalents. Rows show only fields with a confident one-to-one match.
— = no confident one-to-one equivalent in that provider's published field names. BetterJobs Techmap employment_typecontractType
Other Techmap fields, per its reference, and where that information lives in BetterJobs:
Techmap field
BetterJobs
Note
isDuplicate
none needed
Duplicates are merged. sources[] lists every source that saw the job
jsonLD
the canonical job fields
Normalized, not passed through. See the field dictionary
dateActive
—
No confident equivalent. Compare with first_seen_at, last_seen_at, last_verified_at
workPlace
—
No confident equivalent. Compare with location.* and location.remote
Per unique job, not per row. Techmap prices by jobs delivered ($1 per 1,000 via the API, per Techmap). BetterJobs charges 1 credit per unique canonical job. Records from other sources that describe the same opening are merged for free.
Re-reads are free. A job you already paid for returns free in later searches. Overlapping daily windows cost nothing extra.
Estimates are free in both. /count on Techmap, dry_run on BetterJobs.
Proof of charge. GET /v1/billing/ledger?job_id=... lists each charge and each free re-read.
Techmap is a partner provider: Growth includes two partner providers, Pro and above include all six. GET /v1/providers shows whether your plan enables techmap. Plans are on Credits and billing .
Replace file ingestion with API pages. Async search plus cursor paging replaces downloading daily files.
Delete the dedup step. No isDuplicate filter. Key rows on the canonical id.
Delete your JSON-LD mapping. Read normalized fields such as title, location, employment_type, salary directly.
Map contractType to employment_type. Values are full_time, part_time, contract, internship, temporary or null. See Taxonomies .
Handle closures with events. Use job.closed from a watch instead of diffing daily files. See Detect hiring changes .
Country count is not coverage
Per Techmap, it claims 250 countries, about 125 with more than 100 jobs per month. BetterJobs coverage per country is not published during the preview. Measure on your own queries with metadata.providers.contributions and metadata.field_coverage.
posted_within_days counts from first_seen_at. It is the earliest sighting across all sources.
No parquet in v1 preview. Write the JSON pages to parquet yourself if your warehouse needs it.
null is unknown. employment_type: null means no source said, not “other”.