Workana Projects & Client Demand Scraper (LATAM) avatar

Workana Projects & Client Demand Scraper (LATAM)

Pricing

Pay per event

Go to Apify Store
Workana Projects & Client Demand Scraper (LATAM)

Workana Projects & Client Demand Scraper (LATAM)

Scrape public project listings from Workana, Latin America's largest freelance marketplace: budget, skills, client country, proposal count and client reputation signals. Only filters that Workana actually applies. Declares its own coverage ceiling. No login, no session cookies.

Pricing

Pay per event

Rating

0.0

(0)

Developer

Oswaldo Carabano

Oswaldo Carabano

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

2 hours ago

Last modified

Share

Scrape public project listings from Workana, Latin America's largest freelance marketplace. Budgets parsed into numbers, client country, proposal counts and client reputation signals — plus a coverage report that tells you what the run could not reach.

No login. No session cookies. Nothing that expires.


Two things this actor does that the alternatives do not

1. Every filter it offers actually filters

Workana accepts a lot of URL parameters with HTTP 200 and then ignores them. We tested each one and found thirteen that look like filters and do nothing: budget_min, budget, is_hourly, project_type, duration, published, date, urgent, payment_verified, max_bids, featured, hourly, budget_from.

Offering any of those would promise a filter that does not exist — you would pay for rows you did not ask for. So this actor offers only the five that Workana really applies: category, subcategory, skill, country and free-text search.

language is not offered either, and that one is worth explaining: it does not narrow results, it widens them. Measured on the live site:

QueryProjects declared
country=BR2,826
country=BR + language=pt10,000 (Workana's cap)

2. It tells you what it could not reach

Workana serves 7 projects per page and stops at page 50. That is a limit of the site, not of any scraper: about 350 projects per query is the ceiling for everyone. Its own result counter is also capped at 10,000, so for large queries even Workana will not tell you how much there is.

This actor does two things about it:

  • Splits large queries automatically along the country axis, which we measured to be almost purely additive (4% overlap, 6.8× more unique projects across 6 countries).
  • Writes a coverage report — one entry per query, in the key-value store under COVERAGE — with what was declared, what was delivered, whether the page-50 ceiling was hit, and how many projects are out of reach. If a run cannot give you everything, you will know from the output, not by guessing.

Output

One row per project in the default dataset, 54 fields. The coverage report and any failed requests go to the key-value store, under COVERAGE and ERRORS, so no export ever mixes row types.

If a run has no failures, no ERRORS record is created at all — an empty error file would suggest something went wrong when nothing did.

{
"slug": "integracion-de-crm-con-meta-para-atribucion-de-conversiones",
"url": "https://www.workana.com/es/job/integracion-de-crm-con-meta-...",
"title": "Integración de CRM con Meta para Atribución de Conversiones",
"budget_raw": "USD 100 - 250",
"budget_min": 100,
"budget_max": 250,
"budget_currency": "USD",
"budget_unit": "project",
"budget_type": "range",
"is_hourly": false,
"client_name_masked": "Plaga D.",
"client_country_code": "AR",
"client_country_name": "Argentina",
"client_rating": 5,
"client_has_rating": true,
"client_payment_verified": true,
"total_bids": 30,
"total_bids_raw": "Propuestas: 30",
"posted_at": "2026-08-18T19:30:31.906Z",
"posted_at_raw": "Hace 14 horas",
"posted_age_hours": 14,
"skills": ["API", "CRM", "Facebook", "PHP", "Python", "JavaScript"],
"skill_slugs": ["api", "crm", "facebook", "php", "python", "javascript"],
"category": "it-programming",
"category_name": "Programación y Tecnología",
"subcategory": "web-development",
"subcategory_name": "Programación Web",
"project_scope": "Crear un nuevo sitio personalizado",
"project_size": "Medio",
"from_cache": false,
"data_age_hours": 0
}

The posting form's answers, extracted

Workana appends the client's answers to its posting form at the end of every description — and they are structured data hiding inside prose. This actor pulls them out into their own fields instead of leaving you to parse them:

FieldPresent in
category, category_name100%
subcategory, subcategory_name100%
project_scope — "Create a new custom site", "Small bug or change"…68%
project_size — Small / Medium / Large30.5%
client_needs, engagement_type, required_availability, time_required, client_currently_has, required_platforms, word_count_rawvaries by subcategory
extra_attributesthe subcategory-specific tail, with its original label

Two things worth knowing:

  • category is always filled, even when you query by subcategory or by skill. The parent-child relationship comes from Workana's own taxonomy, not from a guess.
  • extra_attributes is by design, not a gap. Workana asks questions specific to each subcategory — "Logo type", "App type", "Payment processor" — and that tail is endless. The cross-cutting questions get their own columns; the rest travels in extra_attributes with its original label, so nothing is lost and the schema does not grow columns that are almost always empty.
  • description_text no longer repeats that block. The metadata comes out as fields, so leaving it in the text too would just inflate the largest field in the dataset. description_html keeps Workana's original, untouched.

Every parsed field keeps its original

budget_min: 100 comes with budget_raw: "USD 100 - 250". total_bids: 30 comes with total_bids_raw: "Propuestas: 30". The parsed number is our interpretation; the raw string is Workana's data, and you can always check our work.

This matters more than it sounds. Workana writes its budgets in the language you request, and the thousands separator changes with it: "USD 1,000" in English is one thousand, "USD 1.000" in Spanish is also one thousand. A parser that ignores the language turns one of them into 1 — a three-orders-of-magnitude error in the field people filter on most. Every row carries its locale so you can verify.

budget_type — why a missing number is not a missing value

A quarter of Workana budgets have no lower bound at all: the client posted "Less than USD 50", not a range. Reporting budget_min: null there and calling it a coverage gap would be wrong — there is no minimum to report. And some projects carry no figure whatsoever ("Open"), which is a different thing again.

So every row says which case it is:

budget_typeMeaningminmaxMeasured (n=272)
range"USD 100 - 250"70.6%
max_only"Less than USD 50" — no lower bound existsnull25.0%
min_only"Over USD 3,000" — no upper bound existsnull4.4%
fixeda single figure✅ (same)rare
unspecifiedWorkana shows no figure at all ("Open")nullnullrare
nullthe listing did not carry the fieldnullnullnot seen

Sort or filter on budget_type before you compare numbers. Averaging budget_min across a mixed set silently drops the quarter of projects that never had one, which biases the average upward. And unit matters too: some budgets are

"USD 15 - 45 / hour"
, so budget_unit is hour there, not project.

The distinction is decided by structure, not vocabulary: a budget string with no digits is unspecified in any language. We only ever observed the English wording ("Open"), so guessing at Spanish and Portuguese labels would have been inventing data we had not measured.


Field coverage — measured, not estimated

Measured on 718 projects across 12 subcategories and 8 countries. We publish how often a field carries useful information, not how often the key exists:

FieldUseful in
slug, title, description, budget_raw, budget_type, client_country_*, total_bids, posted_at, client_name_masked100%
budget_max95.6%
budget_min75.0% — and that is not a gap, see below
skills, skill_slugs98.6%
last_client_message_raw51.7%
client_rating19.4% — see below
client_payment_verified (true)18.9%
is_hourly (true)11.7%
deadline_days1.9%
client_plan0.4%

⚠️ About client_rating

Workana returns a rating for every project, but 80.5% of them are 0.00, which means "no ratings yet" — not "rated zero". That is why every row also carries client_has_rating. If you filter on rating without checking that flag you will discard four fifths of the market believing you discarded bad clients, when you discarded clients with no history.

We could have published "client rating: 100% coverage". It would have been true and misleading.

Fields we do not return

isUrgent, isCompany and isInvite were false in all 718 projects — an anonymous visitor never sees them set. A column with one constant value is not data, so they are not in the output. shortDescription was empty in all 718.


Input

OptionNotes
categoryOne of the 8 top-level categories. Leave empty to sweep all of them
subcategorye.g. web-development, mobile-development, logo-design
skille.g. python, react-js, laravel. One skill applies — Workana keeps the first value and silently drops the rest
countryClient country, ISO-2: AR, BR, MX, ES, CO, PE
queryFree-text search. The only axis without a fixed vocabulary
maxItemsHard cap. Default 500
localees, en or pt — changes the language of Workana's own text
maxCacheAgeHoursDefault 6. Set to 0 to force fresh data
maxConcurrencyDefault 3, and that number is measured (see below)

Slugs are checked before the run starts

A wrong skill slug returns zero projects with HTTP 200 on Workana: react gives nothing, react-js gives hundreds. That is the worst kind of failure — an empty run that looks like a correct one. This actor checks the slug first and tells you what is wrong instead of charging you for nothing.


Freshness

Proposal counts move fast. Measured across 401 projects: the median goes from about 1 proposal in the first hours to about 10 by the twelfth. If you are using total_bids to judge competition, a stale row is a wrong row.

So the default cache age is 6 hours, and every row declares from_cache, fetched_at and data_age_hours. Set maxCacheAgeHours: 0 for live data. You always know how old a row is.


Speed and politeness

maxConcurrency defaults to 3, and that is a measurement: 180 requests at concurrency 1, 2 and 3 completed with zero failures, while concurrency 5 produced 5% HTTP 503 responses from Workana's own servers. Raising it makes runs less reliable, not faster — which is why the maximum is 5 and not 20.


Billing

Pay per event, and two rules:

  • Failed requests are never charged. If a request fails, you get an entry under ERRORS in the key-value store explaining what happened, free.
  • Charges happen right after a row is delivered, never in a batch at the end. If a run is aborted halfway you pay for what you received and nothing more.

What this actor does not do

  • It does not log in, use session cookies, or need credentials.
  • It does not solve captchas.
  • It does not scrape freelancer profiles. Project listings arrive already anonymised by Workana; a freelancer profile is an identified person with their rate and history. That is a different product with a different risk, and it is not in here.
  • It does not promise a coverage percentage. Workana's own result counter is capped, so the true size of the market is unknown — and a percentage of an unknown is a made-up number. You get what was delivered, what was declared, and where the ceiling was hit.