Brazil Tech Jobs Scraper - Stack, Seniority, Salary
Pricing
from $1.70 / 1,000 job listing delivereds
Brazil Tech Jobs Scraper - Stack, Seniority, Salary
Brazilian tech jobs from Programathor and Remotar with stack, seniority, salary band, contract type (PJ, CLT), work model (remote, hybrid, on site) and English requirement read from the posting, no AI. Filter by any of them. No login. US$ 2 per 1,000 jobs; failed items are free.
Pricing
from $1.70 / 1,000 job listing delivereds
Rating
0.0
(0)
Developer
Scrapewise Data
Maintained by CommunityActor stats
0
Bookmarked
4
Total users
4
Monthly active users
8 days ago
Last modified
Categories
Share
Returns open software jobs posted in Brazil, one row per job, with the job title (vaga), the hiring company (empresa), the technology stack as a list (stack), the seniority (senioridade), a remote flag (remoto), the salary band in reais (faixa) and the link to the posting (url). Every job also carries its contract type (contractType: PJ, CLT, internship...), its work model (workModel: remote, hybrid, on site) and whether English is required (englishRequired), read from explicit mentions, no AI.
Built for companies hiring developers in Brazil, recruiters building shortlists by stack, analysts benchmarking Brazilian salaries and nearshore capacity, and developers tracking demand for a technology. AI agents can call it through the Apify MCP server.
Sources
- Programathor (default): a Brazilian board with technology roles only. Every card already carries stack tags, seniority, contract, salary band and location. The listing is plain server-rendered HTML, read with HTTP requests, up to 60 pages per run. It keeps a few hundred open jobs plus an archive of expired ones.
- Remotar (optional,
sources): a Brazilian board of remote and hybrid jobs. The Actor reads only its technology categories (programming, mobile, DevOps, sysadmin, QA, data, UX/UI), newest first: over 5,000 open jobs on 2026-10-09. Remotar has no stack tags, sostackis built from the technologies named in the title, description and requirements. Remotar rows also carrypostedAtandapplyUrl.
With both boards on, pages are read in turns, maxItems is shared, and a job found on both (same title, same company) is delivered once.
At a glance
| Apify plan | Per 1,000 jobs |
|---|---|
| Free | US$ 2.00 |
| Bronze | US$ 1.90 |
| Silver | US$ 1.80 |
| Gold and above | US$ 1.70 |
One event, "Job listing delivered", charged once per job added to the dataset. No monthly fee, no start fee, proxy included. 30 jobs cost US$ 0.06.
Not charged: expired postings (skipped by default), jobs removed by your filters, the same job found on both boards or twice in one run, jobs already delivered when onlyNewJobs is on, and a run that fails.
Input examples
Every field is optional; an empty input returns the newest 30 open jobs from Programathor. The input form describes each field.
Python jobs, remote only:
{"stack": ["python", "django"],"remoto": true,"maxItems": 50}
Both boards, remote PJ jobs only:
{"sources": ["programathor", "remotar"],"contractTypes": ["pj"],"workModels": ["remote"],"maxItems": 50}
A daily job alert: turn on onlyNewJobs, save the input as a Task and attach a daily Schedule. Each run then delivers, and charges, only jobs not delivered before with the same filters. A day with nothing new costs nothing.
{"stack": ["kubernetes"],"senioridade": "senior","onlyNewJobs": true,"maxItems": 50}
stack keeps a job when any of your terms appears in its stack as a substring, so java also matches JavaScript.
Output example
A real Remotar row from a run with both boards, contractTypes ["pj"] and workModels ["remote"] on 2026-10-09 (run RL03ERsZQjhxL3vl4, build 0.1.13):
{"fonte": "remotar","vaga": "Senior Backend Developer","empresa": "Quartile","stack": ["Python", "Flask", "C#", ".NET", "SQL", "SQL Server", "Azure", "Git"],"senioridade": "senior","remoto": true,"faixa": null,"presencial_somente": false,"vencida": false,"url": "https://remotar.com.br/job/165752","coletado_em": "2026-10-09T03:18:05.884186+00:00","url_origem": "https://remotar.com.br/job/165752","location": "Remoto","postedAt": "2026-10-07T21:06:23.590-03:00","applyUrl": "https://ats.rippling.com/pt-BR/careers-quartile/jobs/65ea83ce-24d2-433d-90d6-cabe570fdce1?utm_source=remotar","contractType": "pj","contractTypesFound": ["pj"],"workModel": "remote","englishRequired": true,"classificationEvidence": {"contractType": "tag: pj","workModel": "tag: 100% remoto","englishRequired": "excellent english communication skills (fl"}}
Output fields
Fill rates from a real run of 80 Programathor rows on 2026-09-11 (run X7aOuRosJlRffBdhf).
| Field | What it holds | Filled |
|---|---|---|
fonte | programathor or remotar | 100% |
vaga | job title as posted | 100% |
empresa | hiring company | 100% |
stack | technologies, as a list | 100% |
senioridade | estagio, junior, pleno or senior; null when no level is named | 100% |
remoto | true when the location says remote | 100% |
faixa | monthly salary band in reais, as text ("Até R$5.000", "Acima de R$18.000") | 16% to 39% |
presencial_somente | true when the card carries the on-site only badge | 100% |
vencida | true when the posting is in the expired archive | 100% |
url, url_origem | link to the posting | 100% |
coletado_em | ISO 8601 collection time | 100% |
contractType | pj, clt, internship, temporary, freelance, multiple or unknown | see below |
contractTypesFound | every contract type stated, e.g. ["pj", "clt"] | when stated |
workModel | remote, hybrid, onsite or unknown | see below |
englishRequired | true when English is stated as a requirement | 100% |
classificationEvidence | the text each of the three values above came from | 100% |
location | location line as published | 100% |
postedAt, applyUrl | posting date and the company's application page; Remotar only | Remotar 100% |
The original field names are in Portuguese because the sources are Brazilian; the fields added on 2026-10-09 are in English. "Pleno" sits between junior and senior, closest to mid level. Brazilian companies are not required to publish pay, so faixa is often null and is a band, never a number.
What it fixes
Brazilian job postings leave contract, work model and English in free text, and a keyword search for "PJ" or "remoto" returns banking products, benefits and hybrid cloud jobs. This Actor reads those three values only from an explicit mention, in this order:
- The job title: "Desenvolvedor Python (PJ, 100% remoto)", "Estágio em Dados".
- A field of the board: the contract and location lines of a Programathor card, the CLT, PJ, internship, remote, hybrid and English labels of a Remotar job.
- The description and requirements (Remotar): "Regime: PJ", "Contratação: CLT", "modelo híbrido", "presencial 3x por semana" (read as hybrid), "inglês avançado".
The first place with an answer decides. "CLT ou PJ" gives multiple, and the contractTypes filter matches either. Deliberately not read: "conta PJ", "crédito PJ", "clientes pessoa jurídica", "legislação trabalhista (CLT)", "auxílio home office", "ambientes híbridos". English that is "um diferencial" or "desejável" gives englishRequired: false. Nothing explicit means unknown, never a guess.
Measured on 2026-10-09: Programathor contract and work model filled in 26 of 26 open jobs; on Remotar, work model in 200 of 200 and contract in 89 of 200 (the rest never stated it). A hand check of 60 jobs against the full posting found no wrong value.
Other fixes shipped before release: the seniority output uses the same values as the input filter, so senior matches; expired postings are skipped instead of charged; badges printed inside the title moved into vencida and presencial_somente.
Speed and reliability
Real runs on Apify, HTTP requests only, through the datacenter proxy:
| Input | Rows | Time |
|---|---|---|
Default, maxItems 50 | 50 | 10 s |
stack python, open jobs only, maxItems 200 | 12 | 6 s |
stack python, expired included, maxItems 80 | 80 | 24 s |
The 12 rows in the second run were every open Python job that day: maxItems is a ceiling, not a promise of volume.
Use it from code and AI agents
from apify_client import ApifyClientclient = ApifyClient("YOUR_APIFY_TOKEN")run = client.actor("scrapewise/vagas-tech-br").call(run_input={"stack": ["python"], "senioridade": "senior"})for job in client.dataset(run["defaultDatasetId"]).iterate_items():print(job["vaga"], job["empresa"], job.get("faixa"), job["url"])
The same works from Node.js, the HTTP API (run-sync-get-dataset-items), n8n, Make and Zapier. For AI agents, add the Apify MCP server with this Actor as a tool: https://mcp.apify.com/?tools=scrapewise/vagas-tech-br.
FAQ
What does "vaga" mean? It is Portuguese for a job opening; "vagas tech" is how people in Brazil search for tech jobs.
Why did my run return fewer rows than maxItems? The boards had fewer open jobs matching your filters.
Why does a run with no results fail? On purpose: an empty dataset reported as success hides a schedule that stopped working. The message says whether the filters were too narrow or the layout changed.
What about expired postings? Skipped and not charged by default. With incluirVencidas on they are delivered with vencida: true and charged like any other row.
Does it collect personal data? No. It reads public job postings without login; every field is about a job or a company, never a candidate or recruiter.
Other Brazilian job boards? scrapewise/gupy-vagas and scrapewise/catho-vagas cover general jobs. Open an issue if you need another tech board here.
Limitations
- Two sources, not the whole Brazilian market. Remotar covers remote and hybrid jobs only.
locationis the published text; there is no city filter.- Remotar
stackcomes from technologies named in the text, from a fixed list of about 85 plus your ownstackterms. - No job description or contact details in the row;
urlopens the full posting. - Up to 60 listing pages per run.
Errors
- The source answers anything other than HTTP 200, or the card layout stops matching: the run fails with a message naming the problem.
- Zero rows collected: the run fails instead of finishing with an empty dataset.
- Something broke or a field is missing? Open an issue on the Issues tab.
Changelog
- 2026-10-09: Remotar as an optional second source, with cross-board deduplication,
postedAtandapplyUrl. Every row now hascontractType,workModel,englishRequired,locationandclassificationEvidence, plus thecontractTypes,workModelsandkeywordsfilters. Existing inputs return the same jobs and charges. - 2026-10-04:
onlyNewJobsfor scheduled job alerts. - 2026-09-14: Discounts by plan; every input field got a default, so an empty API call runs.