Upwork Freelancer Scraper — Rates, Earnings, JSS, Work History avatar

Upwork Freelancer Scraper — Rates, Earnings, JSS, Work History

Pricing

from $2.00 / 1,000 stored profile rows

Go to Apify Store
Upwork Freelancer Scraper — Rates, Earnings, JSS, Work History

Upwork Freelancer Scraper — Rates, Earnings, JSS, Work History

Search Upwork freelancers by keyword, skills, rate, JSS, badge, country, hours and tenure. Per profile: exact earnings, every contract with feedback both ways, education, employment, languages, certificates. Seconds from a maintained census; live search on request. Pay per row, no start fee.

Pricing

from $2.00 / 1,000 stored profile rows

Rating

0.0

(0)

Developer

Moxlade

Moxlade

Maintained by Community

Actor stats

0

Bookmarked

43

Total users

32

Monthly active users

8 hours ago

Last modified

Share

Search Upwork freelancers the way a client would — by rate, Job Success Score, badge, country, hours billed, tenure, hire-again rate and skills — and get what a profile page hides: exact lifetime earnings, hours, every contract with the feedback in both directions, education, employment history, languages, certificates. Answered in seconds from a maintained census of 28,000+ full profiles (69,000+ indexed, refreshed daily), with a live search on request. One event per delivered row, no start fee, a zero-row run costs nothing.

Why this one

LiveliveSearch returns Upwork's current result in its own order. fullProfiles reads a profile and its entire contract history on the spot.
StoredA maintained census answers in seconds. $4 per 1,000 rows, no start fee, zero-row runs free.
Filter inputsJSS, hours billed, lifetime earnings, hire-again rate, tenure, badge, country, region, skills — inputs, not just output columns. Hourly rate is numeric (rateMin/rateMax), not a preset bracket.
Verified25 of 25 rows inside the requested band, re-checked on every release run.
The lane, 2026-09-11All 11 Upwork freelancer scrapers, same query: 4 returned any rows, 0 could filter by a numeric rate. Comparison and harness published — run it with your own token.

Top Rated Python developers in the US, $50–100/h

Give it this:

{
"query": "python developer",
"topRated": true,
"countries": [
"United States"
],
"rateMin": 50,
"rateMax": 100,
"maxItems": 25
}

Back comes one record per freelancer — the fields below are the ones this real row carried; every row has the same 65, and a field the source did not show arrives as null, not as a zero:

{
"ciphertext": "~0165a1005d516f24fc",
"url": "https://www.upwork.com/freelancers/~0165a1005d516f24fc",
"person_id": "424140398147256320",
"name": "Denis G.",
"title": "AI Developer | AI Voice Agent | AI Automation | AI Agent",
"overview": "I was building software before 🤖 ChatGPT made “AI expert” everyone’s favorite job title.\n\nHello, I’m an AI Developer & A …",
"country": "United States",
"country_code": "US",
"region": "Americas",
"city": "West Palm Beach",
"state": "FL",
"timezone": "UTC-04:00 Eastern Time (US & Canada)",
"rate": 90,
"jss": 100,
"jss_hidden": false,
"total_earnings": 33532.24,
"earnings_precision": "exact",
"earnings_hidden": false,
"badge": "top_rated_plus",
"top_rated": true,
"top_rated_plus": true,
"hours": 1,
"jobs_total": 10,
"jobs_hourly": 1,
"jobs_fixed": 11,
"contracts_total": 9,
"member_since": "2009-09-14",
"last_worked_on": "2026-06-12",
"hire_again_pct": 100,
"rating": 5,
"feedback_count": 8,
"recent_hours": 0,
"recent_jobs": 2,
"availability": "fullTime",
"contract_to_hire": true,
"id_verified": false,
"phone_verified": true,
"skills": ["TensorFlow", "Python", "JavaScript", "Artificial Intelligence", "Machine Learning", "Deep Learning", "Data Science", "Neural Network", "Artificial Neural Network", "… 11 more"],
"portfolio_count": 13,
"agency_name": "AI Visionary Labs",
"agency_ciphertext": "~01eadb1277dc0075d8",
"status": "public",
"source": "stored",
"completeness": "profile",
"fetched_at": "2026-09-09T11:30:42.836752Z",
"age_days": 7,
"first_seen_at": "2026-09-09T06:34:37.686125Z",
"last_seen_at": "2026-09-17T07:03:11.075062Z",
"card_seen_at": "2026-09-17T07:03:11.075062Z",
"versions": 2,
"contracts_complete": false,
"sections_fetched_at": "2026-09-12T20:26:35.309224Z",
"rank": 5
}

The same actor, asked differently:

  • Active, proven: JSS 95+, 1,000+ hours, worked in the last 60 days — {"jssMin": 95, "hoursMin": 1000, "lastWorkedWithinDays": 60, "sort": "jss", "maxItems": 50}
  • Look up profiles by URL — {"profileUrls": ["https://www.upwork.com/freelancers/~01d699cdd471617458"]}
  • Profiles with their contract history — {"query": "django", "topRated": true, "include": ["contracts", "languages", "education"], "maxItems": 25}
  • Fresh full profiles, every contract — {"profileUrls": ["~01d699cdd471617458", "~01b237e28ffe15f101"], "fullProfiles": true}
  • What changed since a date — {"query": "python", "changedSince": "7d", "includeStale": true, "maxItems": 100}

What the record holds that a profile page does not

Filters a page scrape cannot do. Job Success Score, hours billed, jobs worked, lifetime earnings, member-since, last-worked-within-N-days, hire-again rate, availability, identity verification and skills are filter inputs here, not just output columns. A numeric rate band (rateMin / rateMax) works as stated — every acceptance run checks 25 of 25 rows.

The stats a profile page hides. Exact lifetime earnings where Upwork exposes the figure (with earnings_precision saying exact or band), total hours, hourly vs fixed jobs, completed contracts, hire-again percentage, average rating, member-since and last-worked dates, availability, contract-to-hire and verification flags — 53 typed fields per row.

History, not a snapshot. Every row carries fetched_at, age_days, first_seen_at, last_seen_at and a versions count. changedSince returns only freelancers whose rate, title, JSS, badge, hours, jobs, skills, availability or hire-again rate moved since a date — with the changes as {field, from, to, at}.

Contract history with feedback in both directions. include: ["contracts"] adds each freelancer's completed contracts — title, type, dates, hours, charges, client name, the client's feedback with its six detail scores, and what the freelancer wrote about the client. fullProfiles: true re-reads a profile now and stores every contract it has, charges included on private ones.

The sections a profile page does not show a visitor. Education, employment history, languages with proficiency, certificates and other experiences — on every full profile read, and on stored rows as they are refreshed (sections_fetched_at says when). include adds them to any row.

Private profiles are an answer. Look up a list of ids and the ones that went private come back as status: private with private_since and their last public state (5,000 such profiles tracked, re-checked weekly), instead of silently missing.

Seconds, not minutes. A stored search answers 25 rows in well under 5 s and 500 rows in one call; it cannot be blocked or rate-limited because it reads a maintained table. liveSearch: true asks Upwork for the current result in its own order when you need that.

All 65 fields of a row

Every row carries all of them. A field the source did not show arrives as null, never as a zero or an empty string, and the note says when that happens.

Who they are

fieldtypewhat it holds
ciphertextstringUpwork's stable profile id (~01…). Deduplicate and join on this. Filled on every row.
urlstringThe public profile URL. Filled on every row.
person_idstringUpwork's numeric person id. Filled on every row.
namestringDisplay name as Upwork shows it (first name + initial). Filled on every row.
titlestringThe profile headline. Filled on every row.
overviewstringThe profile overview, full text. Filled on every row.
countrystringCountry as shown on the profile. Filled on every row.
country_codestringISO 3166-1 alpha-2. Filled on every row.
regionstringUpwork's region: Americas, Asia, Europe, Africa, Oceania. Filled on 9% of rows.
citystringCity. Filled on every row.
statestringState or province, where Upwork shows one. Filled on 99% of rows.
timezonestringTimezone as Upwork labels it (e.g. UTC+08:00 …). Filled on 9% of rows.
agency_namestringAgency the freelancer belongs to, when shown on the card. Filled on 3% of rows.
agency_ciphertextstringThat agency's Upwork id. Filled on 3% of rows.

Rate, earnings and score

The numbers a client shortlists on — and the flags that say when Upwork itself is hiding one.

fieldtypewhat it holds
ratenumberListed hourly rate, USD. Filled on every row.
jssintegerJob Success Score, 0–100. Null when Upwork shows none (new, or hidden). Filled on 90% of rows.
jss_hiddenbooleanThe freelancer hides their JSS. Filled on 9% of rows.
total_earningsnumberLifetime earnings, USD. Exact when earnings_precision is exact; a lower bound ($10K+ → 10000) when band. Filled on 48% of rows.
earnings_precisionstringexact (read from Upwork's search payload), band (the card's rounded figure), or null. Filled on 48% of rows.
earnings_hiddenbooleanThe freelancer hides their earnings. Filled on every row.
badgestringtop_rated_plus, top_rated, rising_talent, or null. Filled on 74% of rows.
top_ratedbooleanTop Rated (includes Top Rated Plus). Filled on every row.
top_rated_plusbooleanTop Rated Plus. Filled on every row.
hire_again_pctnumberShare of clients who would hire again, 0–100. Null when Upwork does not show the stat (it needs enough client feedback first). Filled on 74% of rows.
ratingnumberAverage client rating, 0–5. Filled on every row.
feedback_countintegerContracts with client feedback. Filled on every row.

Volume and tenure

fieldtypewhat it holds
hoursnumberTotal hours billed on Upwork. Filled on every row.
jobs_totalintegerJobs worked, as the profile reports it. Filled on every row.
jobs_hourlyintegerHourly contracts. Filled on every row.
jobs_fixedintegerFixed-price contracts. Filled on every row.
contracts_totalintegerCompleted contracts in the work history. Filled on every row.
member_sincestringAccount creation date. Filled on every row.
last_worked_onstringDate of the most recent work on Upwork. Filled on every row.
recent_hoursnumberHours billed in the recent window Upwork reports. Filled on every row.
recent_jobsintegerJobs in the recent window Upwork reports. Filled on every row.

Availability, verification, skills

fieldtypewhat it holds
availabilitystringfullTime, partTime, notSure (as needed). Filled on every row.
contract_to_hirebooleanOpen to contract-to-hire. Filled on every row.
id_verifiedbooleanIdentity verified by Upwork. Filled on every row.
phone_verifiedbooleanPhone verified by Upwork. Filled on every row.
skillsarraySkills in the profile's own order. Filled on every row.
portfolio_countintegerPortfolio items. Filled on every row.

Contracts

With include: ["contracts"] or a full profile read: each contract with its feedback in both directions.

fieldtypewhat it holds
contractsarrayWith include=contracts: completed contracts, newest first — title, type, dates, hours, rate, charges, client name, the client's feedback (score, comment, six detail scores) and the freelancer's feedback about the client. A full profile fetch stores every contract; a stored row carries the page the profile showed (contracts_total says how many exist). Filled on 0% of rows.
contracts_in_progressarrayWith include=contracts: contracts open at the last read. Filled on 0% of rows.
contracts_completebooleanTrue when every contract is stored (a full profile fetch happened); false when only the profile page's newest ones are. Filled on every row.

The sections a visitor does not see

Attached by include, and on every full profile read. sections_fetched_at says when they were last read.

fieldtypewhat it holds
portfoliosarrayWith include=portfolios: portfolio items — title, role, description, URL, skills. Filled on 0% of rows.
testimonialsarrayWith include=testimonials: client testimonials on the profile. Filled on 0% of rows.
specialized_profilesarrayWith include=specialized_profiles: the freelancer's specialized profiles and their occupations. Filled on 0% of rows.
educationarrayWith include=education: schools — institution, degree, area of study, from/to. Filled on 0% of rows.
employment_historyarrayWith include=employment_history: employment outside Upwork — company, title, city, country, from/to, current, description. Filled on 0% of rows.
languagesarrayWith include=languages: languages with proficiency (basic, conversational, fluent, native or bilingual) and whether Upwork verified it. Filled on 0% of rows.
certificatesarrayWith include=certificates: certificates — name, provider, earned on, expires on, verified. Filled on 0% of rows.
other_experiencesarrayWith include=other_experiences: the profile's other experience entries — subject and description. Filled on 0% of rows.
sections_fetched_atstringWhen education, employment, languages, certificates and other experiences were last read; null when never (they are read on every profile fetch since 2026-09-12). Filled on 99% of rows.

What the census remembers

The half a live scrape cannot have: when the profile was first and last seen, what has changed since, and whether it is still public.

fieldtypewhat it holds
versionsintegerHow many distinct states of this profile the census has recorded. Filled on every row.
changesarrayWith changed_since: the fields that moved in the window, as {field, from, to, at}.
first_seen_atstringWhen the census first saw this person. Filled on every row.
last_seen_atstringWhen the census last saw this person (search or profile). Filled on every row.
card_seen_atstringWhen this person last appeared in a search result we read. Filled on 9% of rows.
fetched_atstringWhen this row's data was read from Upwork. Filled on every row.
age_daysintegerDays since fetched_at. Filled on every row.
completenessstringprofile (full profile read) or card (search card only — the stats fields are null). Filled on every row.
sourcestringstored (answered from the census) or live (fetched for this run). Filled on every row.
statusstringpublic, or private when the profile is no longer public — then private_since says since when and the other fields are the last public state. Filled on every row.
private_sincestringWhen we first saw the profile private. Filled on 0% of rows.

How the row was reached

fieldtypewhat it holds
rankintegerPosition in the result set (1-based).

Ways to run it

Stored search with filters. Set query and any filters; rows come from the census in seconds, sorted by relevance, rate, hours, JSS, earnings or recency. Use maxItems to cap rows and spend. Ready-made: Top Rated US Python developers at $50–100/h and Active and proven: JSS 95+, 1,000+ hours, worked recently.

Look up specific profiles. Pass profileUrls (URLs, ~01… ids, or custom URLs like upwork.com/freelancers/<username>). Private profiles answer as status: private; ids the census has not seen yet are listed in the run's status message. A custom URL the census has never followed is resolved on its first run with fullProfiles: true and answered from the census after that.

What changed since last week. Set changedSince to a date or a window (7d, 2w, 1m): only profiles that moved come back, each with a changes list. Pair it with includeStale: true to cover the whole census. Ready-made, schedule it weekly: What changed this week.

Full profiles with every contract. Pass profileUrls with fullProfiles: true: each profile is read from Upwork now, its complete contract history fetched and stored, and the row comes back source: live with contracts, education, employment_history, languages, certificates, other_experiences, portfolios, testimonials and specialized_profiles. Billed as profile_live per profile. Ready-made: Full profiles: every contract, feedback both ways, education.

Upwork's own order, right now. Set liveSearch: true with a query (and any of the badge, country, region, rate, JSS or earnings filters): the run fetches the current Upwork result for that query, returns it in Upwork's order as source: live, and those rows join the census. Ready-made: Live search in Upwork's own order.

Everything you can ask for

Who to look for

The words and the places. Leave them all empty and the census answers by relevance over everyone it holds.

inputacceptsdefaultwhat it changes
querystring—Matched against the profile title, overview and skills, in any word form (developer also finds developers). All words must match. Write alternatives with OR (media buyer OR creative strategist), a phrase in quotes, -word to exclude. Leave empty to filter the whole census by the fields below. When the census holds fewer matching profiles than maxItems, the rest come from Upwork's live search, kept to the same every-word match and billed as card_live (not when a filter the live search cannot apply is set).
skillsarray—Skill names as Upwork lists them, e.g. Python, Django, Web Scraping. A profile matches if it lists any of them.
countriesarray—Country names as Upwork shows them, e.g. United States, United Kingdom, Germany. Any of these.
regionsarray—Upwork's regions. Any of these.
availabilityarray—As stated on the profile: more than 30 hrs/week (fullTime), less than 30 (partTime), as needed (notSure).
excludeAgenciesboolean—Drop freelancers listed under an agency.

The numbers they must hit

Filter inputs, not just output columns: a freelancer is only returned when every one of these is satisfied.

inputacceptsdefaultwhat it changes
rateMininteger—Listed hourly rate at least this.
rateMaxinteger—Listed hourly rate at most this.
jssMininteger—0–100. Profiles without a shown JSS are excluded when this is set.
hoursMininteger—Total hours billed on Upwork.
jobsMininteger—Jobs worked, as the profile reports it.
earnedMininteger—Uses the exact figure where known, the card's lower bound otherwise.
hireAgainMininteger—Share of clients who would hire again.
topRatedboolean—Only Top Rated freelancers (includes Top Rated Plus).
topRatedPlusboolean—Only Top Rated Plus.
risingTalentboolean—Only freelancers with the Rising Talent badge.
idVerifiedboolean—Only freelancers whose identity Upwork has verified.
hasContractsboolean—Only freelancers with at least one completed contract.
memberSinceAfterstring—Joined Upwork on or after this date (YYYY-MM-DD).
memberSinceBeforestring—Joined Upwork on or before this date (YYYY-MM-DD).
lastWorkedWithinDaysinteger—Last work on Upwork within this many days — active freelancers only.

Look up people you already have

Profile URLs, ~01… ids or custom URLs. Ids the census has not seen are named in the run's status message instead of silently missing.

inputacceptsdefaultwhat it changes
profileUrlsarray—Look up specific freelancers instead of searching: profile URLs (https://www.upwork.com/freelancers/~01…), bare ids (~01…), or custom profile URLs (https://www.upwork.com/freelancers/
includePrivatebooleanfalseSearch results also return profiles that are no longer public (status: private, with private_since and the last public state). Lookups by id always answer them.

What to attach to each row

Contracts, portfolios and the profile sections a visitor does not see. Attached blocks are not charged separately.

inputacceptsdefaultwhat it changes
includearray—Sections to add to every row from the stored profile at no extra charge: contracts (completed contracts with feedback both ways; the full list after a fullProfiles read), portfolios, testimonials, specialized_profiles, education, employment_history, languages, certificates, other_experiences.
fullProfilesbooleanfalseWith profileUrls: re-read each profile from Upwork now and fetch everything it has — the complete contract history (hours, charges also on private contracts, the client's feedback and the freelancer's feedback about the client), education, employment history, languages, certificates, other experiences, portfolios, testimonials and specialized profiles. Billed as profile_live per profile; about 10 s per profile. Without it, the stored profile is returned (profile_stored).

Stored or live, and how fresh

The census answers in seconds; a live read asks Upwork during your run. Every row says when it was read (fetched_at, age_days).

inputacceptsdefaultwhat it changes
liveSearchbooleanfalseAsk Upwork for the current result for the query instead of the stored census: Upwork's own relevance order, and freelancers the census has not seen yet. Slower (under a minute for 25 rows), billed as card_live per row. Needs a query.
maxPagesinteger10With liveSearch, how many result pages to walk at most (10 rows per page) before returning what passed the filters.
maxAgeDaysinteger30Stored rows read from Upwork longer ago than this are left out of a search. Every row carries fetched_at and age_days regardless.
includeStalebooleanfalseIgnore maxAgeDays and return every matching stored profile.
changedSincestring—ISO date or timestamp, or a window like 7d, 2w, 1m or 7 days (so a scheduled task never needs editing). Only profiles whose rate, title, JSS, badge, hours, jobs, skills, availability or hire-again rate moved since then; each row carries the changes as {field, from, to, at}.

Order and run size

The spending cap: nothing is charged for a row that is not in your dataset.

inputacceptsdefaultwhat it changes
sortrelevance / rate / rate_asc / hours / jss / earnings / last_worked_on / first_seen / fetched"relevance"Order of the stored results. Upwork's own relevance order comes only with liveSearch.
maxItemsinteger50Upper bound on rows returned — and on what the run can cost.

If an input can't be used

The run still ends Succeeded and charges nothing. Its status message begins INPUT REJECTED and says what to change. The dataset holds a single row, {"error": true, "code": …, "message": …}, with no results, and the key-value record ERROR repeats it, so an agent should check error on the first row. A search that finds nothing is not an error, only an empty dataset. A run marked Failed is a fault on our side, not your input; it costs nothing and reaches us.

Before you run it

What sets this apart from other Upwork freelancer scrapers? Measured, not asserted. On 2026-09-11 we ran one query against all 11 Upwork freelancer scrapers then on the store: 4 returned any rows at all, and 0 could filter by a numeric hourly rate — the harness is published, run it with your own token (comparison, code). The structural difference is that this one reads a maintained census instead of opening a search during your run, so it answers in seconds, it keeps the profile's earlier versions (changedSince returns only the freelancers who moved, with the changes), and a profile that went private still answers — as status: private with the date. Price is the other one: $4.00 per 1,000 stored profiles, where the other profile scrapers on the store charge between $0.005 and $0.023 per profile (our own store census, read 2026-09-23). When you need Upwork's live result in its own order, liveSearch does that.

Where does the data come from, and how fresh is it? From a census of public Upwork freelancer profiles that is refreshed every day and re-reads the oldest profiles on a rolling basis. Every row says when it was read (fetched_at, age_days); a stored search leaves out rows older than maxAgeDays (default 30) unless you set includeStale. liveSearch reads Upwork at run time.

Are the earnings exact? Where Upwork exposes the figure, yes — earnings_precision: exact. Where only a rounded band is shown, total_earnings is that band's lower bound and earnings_precision is band. When the freelancer hides earnings, earnings_hidden is true and the figure is null. The share of rows with an exact figure grows as the census re-reads profiles; it is stated on the Output tab from the latest acceptance run.

Why is a profile missing? A stored search only returns profiles the census holds. If you need someone the census has not reached, liveSearch finds them for a keyword, and a lookup by id reports the missing ids in the status message so you can see exactly what was not found.

Is this legal, and what about personal data? Profiles are read from the pages Upwork serves the open web — what a client sees and a search engine indexes — with no login and nothing behind a paywall. The record keeps what a professional profile publishes: title, overview, rates, earnings, hours, ratings, skills, work history, and the country and city the freelancer put on it. No contact details are stored: no e-mail, no phone number, no street address; id_verified and phone_verified are Upwork's own flags, never the values behind them. A profile that goes private is served as status: private with private_since and its last public state, not as fresh data. A freelancer who writes to the support address is removed from every answer this Actor gives and their stored record is dropped. Whether your use is lawful — and, in the EU or the UK, what basis you have for it — depends on what you do with it, and that part is yours, not ours.

How do I get the rows out? They land in the run's dataset: download them as JSON, CSV, Excel or XML from the run page, or read the same rows over the Apify API (the API tab has the call for this Actor). Save a run as a task, schedule it, and point the store's integrations at it — webhooks, Zapier, Make, Slack, Google Drive — or let an agent call the Actor over MCP. Every row carries a stable ciphertext id, so repeated runs deduplicate against your own table, and changedSince keeps the pull incremental.

Where is this documented in more depth? On moxlade.com: what the actor returns, field by field, how to export freelancers to CSV or JSON, how to filter by rate, Job Success Score and country, the official Upwork API against this census, and every Upwork freelancer scraper on Apify measured on one day. The MCP corpus behind the buyer side of Upwork is at buyer.moxlade.com.

Who makes this, and what else is there? Moxlade — corpora your agent can ask. Its other actors are the Vinted seller census (who carries a brand, how far under the market they price, what each closet holds) and the Reddit scraper (subreddit listings, search and full comment trees); its MCP endpoint for Upwork buyer intelligence is buyer.moxlade.com.

Is there example code? Yes — ready-to-run scripts for every job on this page (a filtered shortlist to CSV, a one-call export, enriching a list of profile URLs, stored versus live, full profiles, weekly changes) in Python, JavaScript and curl: github.com/getmoxlade/upwork-freelancers-examples. The comparison with the other Upwork scrapers on the store is reproducible from github.com/getmoxlade/upwork-scraper-comparison.

What it costs

Charged per row, and only once the row is in your dataset, so maxItems is also your spending cap. No start fee, no minimum charge, and an empty run is free: on the release runs the charged event count equalled the row count on every run, and the zero-row run charged $0.00. Speed on the same runs: 25 stored rows in 0.4–1.9 s of run time; 25 live rows in Upwork's order in under 30 s including the search.

eventwhat one event isper 1,000
profile_storedOne freelancer row answered from the census (search or lookup). Charged only after the row is in your dataset.$4.00
card_liveOne row of a live Upwork search: liveSearch: true, or the rows that complete a stored search short of maxItems.$5.00
profile_liveOne freshly read full profile by id.$20.00

Who asks this

Recruiters and talent teams — shortlist by the numbers a client cares about — JSS, hours, hire-again rate, rate band, country, recent activity — and export 500 profiles in one run.

Agencies and freelancers benchmarking — see what people with your skills and badge actually charge and earn, by country and tenure, from real listed rates and exact earnings.

Market and labor researchers — a census with time on it: member-since cohorts, rate and badge changes over time, who went private — questions a live scrape cannot answer.

Product and lead-gen builders — one API-shaped dataset with a stable id (ciphertext) to join on, typed fields, and changedSince for incremental pulls.

Questions

support@moxlade.com

Every figure on this page was measured on our own runs or read from our own tables, on the date given next to it.