If you have ever job-hunted in Brazil, you know the ritual: open a listing, scroll past the requirements, look for the salary, and find nothing. It feels like most postings hide the number. I wanted to know whether that impression survives contact with data, so I pulled 100 listings and counted.
It does. 92 of 100 listings disclosed no salary at all.
The measurement
One query — python, filtered to São Paulo state — on Catho, one of Brazil’s largest job boards. 100 listings. Every number below comes from that dataset.
Salary disclosure: 8%
Count Listings with a numeric salary 8 Listings with no salary 92Eight. Out of a hundred. And the eight that do publish a number are mostly not publishing a range — they publish a floor, phrased as “A partir de R$ X” (starting from R$ X).
The disclosed values, sorted:
R$ 2.000
R$ 2.626
R$ 3.333
R$ 6.426
R$ 6.426
R$ 7.000
R$ 8.000
R$ 12.000
Enter fullscreen mode Exit fullscreen mode
Median: R$ 6.426. Range: R$ 2.000 to R$ 12.000 — a 6x spread across listings that all matched the same python query.
I want to be careful about what this median means. It is the median of the 8 listings willing to publish a number, not the median of the São Paulo Python market. Those are very different populations, and the second one is not measurable from this data. Treat R$ 6.426 as a data point, not a benchmark.
Contract type: the real surprise
I expected CLT (Brazil’s standard employment contract) to dominate. Instead, the largest group states nothing at all:
Contract type Count Not stated 46 CLT (Efetivo) 33 Prestador de serviços (PJ) 16 Cooperado 2 Temporário 2 Autônomo 1Nearly half the listings do not say whether the role is CLT or PJ. For a Brazilian candidate that is a material omission — CLT and PJ differ in taxes, benefits, vacation, and severance. Among the 54 listings that do state it, CLT leads 33 to 16 over PJ, roughly 2:1.
Benefits are disclosed far more often than pay
This is the part I did not expect. Companies that will not tell you the salary will happily list the meal vouchers:
Benefit Listings Health insurance 22 Meal voucher (tíquete refeição) 22 Transport voucher 19 Food voucher (tíquete alimentação) 12 Dental insurance 11 Group life insurance 1122 listings disclose health insurance. Only 8 disclose salary. Benefits appear to be treated as marketing; compensation as negotiation leverage.
Across the sample there were 14 distinct benefit types.
Sponsored listings: 4%
Only 4 of the 100 were paid placements. The rest ranked organically.
Getting the data
Catho renders search results server-side, so pagination is a plain HTTP fetch — 20 listings per page at /vagas/<role>/?page=N. No headless browser needed.
The detail data is the useful part. Each listing has a JSON endpoint:
curl -s "https://oferta.catho.com.br/offer/38220856/d/j?ipo=42&iapo=1" | jq '.o | {
title: .t,
salary: .s,
salary_text: .sn,
contract: .ctns[0],
benefits: .bns
}'
Enter fullscreen mode Exit fullscreen mode
That returns salary as an integer (6426), not a formatted string, plus benefits as a proper array and timestamps in ISO format. Which is why the counting above took minutes instead of an afternoon of regex cleanup.
A caveat for anyone building on this: the field names are single letters (t, s, sn, ld) with no documentation. I mapped them by diffing responses against the rendered page. They could change without notice.
Output shape after normalisation:
{
"jobId": "38220856",
"title": "Especialista em Energia",
"company": "LÍDER BPO",
"salary": 6426,
"salaryText": "A partir de R$ 6.000,00",
"city": "São Paulo",
"state": "SP",
"contractType": "CLT (Efetivo)",
"benefits": ["Assistência médica / Medicina em grupo", "Tíquete refeição"],
"publishedAt": "2026-09-01T16:07:55",
"isSponsored": true,
"url": "https://www.catho.com.br/vagas/especialista-em-energia/38220856"
}
Enter fullscreen mode Exit fullscreen mode
Limits of this sample
Worth stating plainly, because a single query is a narrow window:
-
One query (
python), one state (SP), one day. Different roles and regions will look different. - 100 listings is enough to establish that non-disclosure is the norm — 92/100 is not a marginal result — but too small to say anything reliable about salary levels.
- The salary median rests on 8 data points. Do not build a compensation model on it.
- Catho is one board among several. Gupy, InfoJobs, and LinkedIn may have different disclosure cultures.
The disclosure finding is the robust one here. The salary numbers are illustrative.
If you want to run this yourself
The scraper is on the Apify Store — Catho Jobs Scraper. It handles the pagination and the detail-endpoint mapping, returns the flat JSON above, and is pay-per-use. It is how I pulled the 100 listings in this post.
What I would measure next
The single query is a snapshot; the interesting version is longitudinal. Does disclosure improve over time? Does it differ by seniority — do senior roles hide pay more or less than junior ones? Does it differ between CLT and PJ postings?
Brazil has no salary-transparency law, so any movement would be voluntary. Worth watching whether it happens at all.
If you have measured this on another board, I would like to compare — particularly whether the ~8% disclosure rate holds outside Catho.