Workana Projects & Client Demand Scraper (LATAM)
Only the filters that actually filter.
Extracts public project listings from Workana, Latin America's largest freelance marketplace, with budgets parsed into numbers and their currency, proposal counts, client country and client reputation signals. It offers only the five filters Workana actually applies, and writes a coverage report saying what the run could not reach.
oswaldocarabano/workana-scraper
{
"subcategory": "web-development",
"country": "AR",
"maxItems": 500,
"locale": "es",
"maxCacheAgeHours": 6
}- Version
- v0.1.14
- Memory
- 512 MB
- Browser
- none
- Proxy
- None required
Short answer
The Workana Projects & Client Demand Scraper extracts public project listings from Workana with 55 fields per project: budget parsed into min, max, currency, unit and type, proposal count, client country, and client reputation signals. It offers only the five filters Workana really applies — thirteen others are accepted and silently ignored — and writes a coverage report because Workana serves 7 projects per page and stops at page 50, capping any query at about 350 projects. Pricing is $0.002 per project, and failed requests are never charged.
Key points
- Thirteen URL parameters look like filters on Workana and do nothing: `budget_min`, `budget`, `is_hourly`, `project_type`, `duration`, `published`, `date`, `urgent`, `payment_verified`, `max_bids`, `featured`, `hourly`, `budget_from`. This Actor offers only the five that Workana really applies.
- `language` is not offered because it does not narrow results, it widens them: `country=BR` declares 2,826 projects, and `country=BR` plus `language=pt` declares 10,000 — Workana's cap.
- Workana serves 7 projects per page and stops at page 50, so about 350 projects per query is the ceiling for everyone. Large queries are split along the country axis, which measured almost purely additive: 4% overlap and 6.8x more unique projects across 6 countries.
- Every parsed field keeps its original: `budget_min: 100` travels with `budget_raw: "USD 100 - 250"`, so the interpretation is checkable.
- `budget_type` distinguishes a missing number from a number that does not exist: 25.0% of budgets are `max_only` ("Less than USD 50"), which has no lower bound to report.
- 80.5% of client ratings are `0.00`, meaning "no ratings yet" rather than "rated zero", which is why every row carries `client_has_rating`.
- Field coverage measured on 718 projects across 12 subcategories and 8 countries, reported as how often a field carries useful information rather than how often the key exists.
What it does
Extracts public project listings from Workana, Latin America's largest freelance marketplace, with budgets parsed into numbers and their currency, proposal counts, client country and client reputation signals. It offers only the five filters Workana actually applies, and writes a coverage report saying what the run could not reach.
{
"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"],
"category": "it-programming",
"subcategory": "web-development",
"project_scope": "Crear un nuevo sitio personalizado",
"project_size": "Medio",
"locale": "es",
"from_cache": false,
"data_age_hours": 0
}Why this one
Every filter it offers actually filters
Workana accepts a lot of URL parameters with HTTP 200 and then ignores them. Each one was tested, and thirteen turned out to be decorative. Offering any of them would promise a filter that does not exist and charge you for rows you did not ask for, so the input exposes only category, subcategory, skill, country and free-text search. The Actor also validates slugs before the run starts, because a wrong skill slug returns zero projects with HTTP 200 — an empty run that looks like a correct one is the worst failure mode there is.
A coverage report instead of an implied percentage
Workana's own result counter caps at 10,000, so for large queries even Workana will not say how much there is. The Actor writes one entry per query under COVERAGE in the key-value store with what was declared, what was delivered, whether the page-50 ceiling was hit, and how many projects are out of reach. It deliberately does not publish a coverage percentage, because a percentage of an unknown is a made-up number.
Budgets parsed with the locale that decides their meaning
Workana writes budgets in the language you request, and the thousands separator moves with it: "USD 1,000" in English and "USD 1.000" in Spanish are both 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` and its `budget_raw`, so the parse can be checked.
The client rating that would have been a lie
Workana returns a rating for every project, so "client rating: 100% coverage" would have been true and misleading: 80.5% of them are 0.00, which means the client has no ratings yet. Filtering on rating without checking `client_has_rating` discards four fifths of the market in the belief that you discarded bad clients.
Use cases
- Measure freelance demand by skill, subcategory and country, using budgets that are parsed and comparable.
- Price your own services against real posted budgets rather than against survey data.
- Find under-served niches: many projects, few proposals, in a subcategory you work in.
- Track how fast competition arrives — proposal counts move from about 1 in the first hours to about 10 by the twelfth.
- Build a lead list of clients posting work in your category, with payment-verified and rating-history signals.
- Study the structured answers clients give Workana's posting form: project scope, size, engagement type and availability.
Input
Every field has a default, and the defaults are deliberately small so a first run is cheap enough to inspect before you commit to a sweep. This table mirrors the Actor's own input schema field for field.
| Field | Default | What it does |
|---|---|---|
categoryselect | null | CategoryOne of Workana's 8 top-level categories. Empty sweeps all of them. |
subcategoryselect | null | Subcategory61 to choose from, grouped by parent. This is the axis that narrows a large category the most. |
skillstring | null | Skill slugA single skill slug as it appears in a Workana URL — `python`, `react-js`, `laravel`. Free text on purpose, because Workana keeps adding skills. The slug is validated before the run starts: `react` returns nothing, `react-js` returns hundreds. |
countryselect | null | Client's countryWhere the client is based. Also the axis the Actor splits large queries along, because it measured almost purely additive. |
querystring | null | Free-text searchThe only axis without a fixed vocabulary, so it is the most flexible way to narrow a large category. |
maxItemsinteger | 500 | Maximum projectsHard cap on projects delivered. Note the site's own limit: 7 per page and no page 51. |
localeselect | "es" | Content language`es`, `en` or `pt`. It changes the wording of budgets, dates and proposal counts — and the thousands separator — so the Actor parses each one accordingly and records which was used. |
maxCacheAgeHoursinteger | 6 | Maximum cache age (hours)Proposal counts move fast: the median goes from about 1 in the first hours to about 10 by the twelfth. Set 0 for live data. |
includeCoverageReportboolean | true | Include coverage reportWrites one entry per query under COVERAGE stating what was declared, what was delivered and whether the ceiling was hit. |
maxConcurrencyinteger | 3 | Maximum concurrencyMeasured, not guessed: 180 requests at concurrency 1, 2 and 3 completed with zero failures, while 5 produced 5% HTTP 503 from Workana's servers. The maximum is 5 for that reason. |
Output and fill rates
A field being in the schema is not the same as it having a value. The percentages below were counted on real runs; the sample sizes are in Measurements. Anything not listed here is not promised.
| Field | Filled | Meaning |
|---|---|---|
slugstring | 100% | Project slug. The join key across runs. |
urlstring | 100% | Canonical project URL. |
titlestring | 100% | Project title as published. |
description_textstring | 100% | Description with the posting-form block removed, since that metadata comes out as its own fields. `description_html` keeps Workana's original. |
budget_rawstring | 100% | Workana's own budget string, kept so the parse can be checked. |
budget_minnumber | 75% | Lower bound. 75.0% — and the gap is mostly budgets that have no lower bound, not a parse failure. |
budget_maxnumber | 95.6% | Upper bound. |
budget_currencystring | 100% | Currency of the budget. |
budget_unitstring | 100% | `project` or `hour`. Some budgets are quoted per hour, which changes what a comparison means. |
budget_typestring | 100% | `range` 70.6%, `max_only` 25.0%, `min_only` 4.4%, plus rare `fixed` and `unspecified`. Sort on this before comparing numbers. |
is_hourlyboolean | 100% | True on 11.7% of projects. |
client_name_maskedstring | 100% | Client name as Workana masks it. Never resolved further. |
client_country_codestring | 100% | ISO-2 country, with `client_country_name`. |
client_ratingnumber | 19.4% | Returned for every project, but 80.5% are 0.00 meaning "no ratings yet". Use `client_has_rating` before filtering. |
client_payment_verifiedboolean | 100% | True on 18.9% of projects. |
total_bidsinteger | 100% | Proposal count, with `total_bids_raw`. Moves fast — a stale row is a wrong row. |
posted_atdatetime | 100% | Publication time, with `posted_at_raw`, `posted_age_hours` and an approximate flag. |
skillsstring[] | 98.6% | Skills as published, with `skill_slugs`. |
categorystring | 100% | Always filled, even when you query by subcategory or skill, from Workana's own taxonomy. |
subcategorystring | 100% | Subcategory, with `subcategory_name`. |
project_scopestring | 68% | From the client's answers to Workana's posting form. |
project_sizestring | 30.5% | Small, medium or large, from the same form. |
last_client_message_rawstring | 51.7% | The client's most recent message on the listing. |
extra_attributesobject | not measured | The subcategory-specific tail of the posting form, with its original labels. By design, not a gap. |
localestring | 100% | Which language Workana rendered — the fact that decides how the budget parses. |
from_cacheboolean | 100% | With `fetched_at` and `data_age_hours`. Default cache age is 6 hours. |
Every key is always present. A field that exists but is empty comes back as explicit null, so a parser never has to guess.
Datasets
Different record types go to different datasets, so the main table never carries columns that are blank on most rows.
defaultOne row per project, 55 fields.billedCOVERAGE (key-value store)One entry per query: declared, delivered, ceiling hit, out of reach.never billedERRORS (key-value store)Anything that failed, with the reason. Not created at all when a run has no failures.never billed
Pricing
Pay per delivered result. Charges are applied as each row is produced rather than in a lump at the end, so an aborted run bills only for what it actually gave you.
| Event | Price | Notes |
|---|---|---|
actor-startActor start | $0.00001 | The platform minimum, charged once per run. A query that finds nothing costs effectively nothing. |
projectProject delivered | $0.002 | One Workana project, 55 fields, with the budget parsed into min and max with its currency and type. Failed requests are never charged. |
Measurements
Each figure is shown with the method that produced it. A benchmark without a method is a marketing claim wearing a number's clothes.
Filters that do nothing
13 of 18 tested
Each URL parameter was tested against the live site. Thirteen returned HTTP 200 and did not change the result set, so they are not offered.
The `language` trap
2,826 → 10,000 declared
`country=BR` declares 2,826 projects; adding `language=pt` declares 10,000, which is Workana's counter cap. It widens rather than narrows, so it is not offered.
Per-query ceiling
~350 projects
7 projects per page, no page 51. A limit of the site, not of any scraper.
Country splitting
4% overlap, 6.8x unique projects
Measured across 6 countries: splitting a large query by country is almost purely additive, which is why it is the split axis.
Coverage sample
718 projects
Across 12 subcategories and 8 countries. Coverage is reported as how often a field carries useful information, not how often the key exists.
Budget shape
70.6% range, 25.0% max-only, 4.4% min-only
n=272. A quarter of budgets have no lower bound at all, so a null `budget_min` there is not a coverage gap.
Concurrency
3 clean, 5 produces 5% HTTP 503
180 requests at concurrency 1, 2 and 3 completed with zero failures. The maximum offered is 5, and it is not the fast setting.
Proposal velocity
~1 proposal in hour one, ~10 by hour twelve
Median across 401 projects. It is why the default cache age is 6 hours rather than a day.
What it will not do
Stated plainly so you can judge fit before spending anything.
- About 350 projects per query is the ceiling: Workana serves 7 per page and stops at page 50. That applies to every tool, not just this one.
- Workana's own result counter caps at 10,000, so the true size of a large query is unknown and no coverage percentage is offered.
- One skill applies per query. Workana keeps the first value and silently drops the rest.
- No freelancer profiles. Project listings arrive anonymised by Workana; a profile is an identified person with their rate and history, which is a different product with a different risk.
- `isUrgent`, `isCompany`, `isInvite` and `shortDescription` are not returned: they were false or empty in all 718 measured projects, and a column with one constant value is not data.
- No login, no session cookies, no CAPTCHA solving.
Privacy
- Client names arrive already masked by Workana — `client_name_masked` — and are passed through as published, never resolved.
- Freelancer profiles are deliberately out of scope: a project listing is anonymised, a profile is an identified person with their rate and history.
- `description_has_contact` flags descriptions where a client published contact details themselves, so you can exclude them from anything you redistribute.
- Removal requests: privacy@actorstack.dev
See also the data removal process.
Frequently asked questions
Why does the Actor offer so few filters?
How many projects can I get from one query?
Why is `budget_min` empty on a quarter of projects?
Can I filter clients by rating?
Does it scrape freelancer profiles?
Why does the language setting matter?
How fresh is the proposal count?
Guides for this Actor
- Scrape WorkanaA walkthrough of extracting LATAM freelance demand: the five filters that work, the coverage ceiling nobody can exceed, and why the language setting changes what a budget means.
- Workana APINo public API, a robots.txt that disallows the internal one, and a set of URL parameters that accept anything. What a compliant integration actually has to work with.
- Filters that do nothing`budget_min`, `is_hourly`, `duration`, `payment_verified` and nine others return HTTP 200 and change nothing. One of them makes the result set bigger.
- The coverage ceilingAbout 350 projects per query is the limit for every tool. The interesting part is that Workana cannot tell you what you are missing either — so the Actor writes a coverage report instead of a percentage.
- Parsing budgets"USD 1,000" and "USD 1.000" are both one thousand. "Less than USD 50" has no lower bound. Both facts break naive budget parsing in ways that survive review.
- The rating that is not a ratingWorkana returns a rating for every project, so "100% coverage" would be true. It would also throw away four fifths of the market if you believed it.
- Workana data fields55 fields measured on 718 projects across 12 subcategories and 8 countries — reported as how often a field carries useful information, not how often the key exists.
- Market analysisWhat listing data supports — demand by skill, budget distributions, competition levels — and the four claims it cannot carry.
- Proposal velocityMeasured across 401 projects: competition on a freelance listing has a half-life measured in hours, which makes a stale row a wrong row.
- Skill slugs`react` returns zero projects with HTTP 200. `react-js` returns hundreds. A wrong slug is the worst kind of failure, so it gets checked before the run rather than after.