# LATAM Jobs Scraper + CNPJ: Gupy, Catho, Computrabajo, OCC (`latsouth/latam-jobs-hiring-signals`) Actor

Job postings from Gupy, InfoJobs, Catho (BR), Computrabajo (MX, CO, AR, PE, CL), Bumeran (AR, PE, MX) and OCC (MX), joined to the official company registry: tax id, legal name, size, activity, website, match confidence. Hiring-companies mode = intent signal. PT: vagas com CNPJ.

- **URL**: https://apify.com/latsouth/latam-jobs-hiring-signals.md
- **Developed by:** [LatSouth Data](https://apify.com/latsouth) (community)
- **Categories:** Lead generation, Jobs
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 job postings

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## LATAM Jobs & Hiring Signals: Gupy, InfoJobs, Catho, Computrabajo, Bumeran, OCC + the company registry

**By [LatSouth](https://latsouth.com).** Search job postings on **Gupy**, Brazil's largest ATS portal (about 80,000
open postings from thousands of companies), and get each posting normalized **plus the hiring company's record from the
official Brazilian company registry**: CNPJ, legal name, size (porte), CNAE activity, HTTP-verified website, with the
**match method and a confidence score**. Or switch to `hiring_companies` mode and get **one row per company that is
hiring**: open jobs, areas, seniority, locations and latest posting, which is a ready-made **hiring-intent signal** for
B2B prospecting.

Also in the same run and the same output: **InfoJobs Brasil** and **Catho** (Brazil), **Computrabajo** (Mexico,
Colombia, Argentina, Peru, Chile), **Bumeran** (Argentina, Peru, Mexico) and **OCC Mundial** (Mexico), each joined to
that country's registry.

Single-board scrapers give you the posting. This actor also tells you **which legal entity is hiring**, so you can join
postings to your CRM, size the company and filter by industry.

| | |
|---|---|
| Boards | Gupy, InfoJobs, Catho (Brazil) · Computrabajo (Mexico, Colombia, Argentina, Peru, Chile) · Bumeran (Argentina, Peru, Mexico) · OCC Mundial (Mexico) |
| Registry | Receita Federal CNPJ open data (10.9M establishments) via the LatSouth registry release |
| Price | **US$0.002 per posting** (`job-result`) · **US$0.008 per hiring company** (`company-result`) |
| Typical run | 200 postings from 143 companies in 2.3 min; 52 hiring companies (300 postings scanned) in 53 s |

### Use cases

- **Hiring-intent prospecting**: companies opening data, sales or engineering roles this week, with CNPJ, size and
  activity, ready for outbound or account scoring.
- **Recruiting and HR-tech market maps**: who hires what, where, remote vs on-site, by seniority and contract type.
- **Labour-market research**: postings by state, city, area and contract type, deduplicated across keywords.

### Input examples

**Data jobs, remote, last 7 days**

```json
{"keywords": ["analista de dados", "engenheiro de dados"], "workplace_types": ["remote"], "posted_within_days": 7,
 "max_results": 200}
```

**Who is hiring sales people in São Paulo state (one row per company)**

```json
{"mode": "hiring_companies", "keywords": ["vendas", "sdr", "executivo de contas"], "state": "SP",
 "max_jobs_scanned": 2000, "max_results": 300}
```

| Field | What it does |
|---|---|
| `mode` | `jobs` (default): one row per posting. `hiring_companies`: postings grouped by company. |
| `keywords` | One search per keyword (Gupy searches job titles); results are deduplicated. Empty = newest postings. |
| `sources` | `gupy`, `infojobs`, `catho`, `computrabajo_mx`, `computrabajo_co`, `computrabajo_ar`, `computrabajo_pe`, `computrabajo_cl`, `bumeran_ar`, `bumeran_pe`, `bumeran_mx`, `occ` (`max_results` is split evenly across boards, a board with fewer postings leaves its share to the others; no keyword = newest postings of each board) |
| `workplace_types` | `remote`, `hybrid`, `onsite` (empty = all) |
| `state`, `city` | Brazilian UF (`SP` or `São Paulo`) and city |
| `posted_within_days` | Only postings published in the last N days |
| `max_results` | Rows to return (postings or companies); you pay per row. Default 25: an empty input `{}` returns 25 postings (about US$0.05), each with the hiring company's registry match in `company`; company rows (`company-result`) come only in `hiring_companies` mode |
| `max_jobs_scanned` | `hiring_companies` mode: postings read before grouping (default 1,000; scanning is free) |
| `enrich` | Registry join (default `true`) |
| `include_description` | First 600 characters of the description, HTML removed, e-mails and phone numbers scrubbed |
| `proxyConfiguration` | Optional Apify Proxy (default off: direct requests), **datacenter only**: a `RESIDENTIAL` group or custom proxy URLs are rejected ("only Apify datacenter proxy is supported"). For IP location and spreading the request rate only: same headers, same pacing, no captcha or bot-challenge solving |

### Output

**Posting row** (`jobs` mode): `board`, `job_id`, `url`, `title`, `company_name` (as shown on the board),
`company_board_id` (career-page subdomain), `company_website` (as declared by the company on its career page),
`company_open_jobs_on_board`, `city`, `state`, `state_code`, `workplace_type`, `contract_type` (`employee`,
`contractor` (PJ), `internship`, `apprentice`, `temporary`, `trainee`, `talent_pool`, ...) + `contract_type_raw`,
`seniority` (from the title only: `intern` ... `director`, null when the title does not say), `department`,
`published_at`, `deadline`, `salary_min` / `salary_max` / `salary_currency` / `salary_text` (only when the posting
labels a salary), `description`, `accepts_pwd`, `scraped_at`, and `company`.

**Company row** (`hiring_companies` mode): `company_name`, `career_page_url`, `company_website`, `linkedin_url`,
`n_open_jobs` (in this scan), `company_open_jobs_on_board` (all open jobs on its career page), `titles_sample`,
`departments`, `seniority_mix`, `workplace_mix`, `contract_mix`, `locations`, `most_recent_posting_at`,
`oldest_posting_at`, `job_urls_sample`, and `company`. Sorted by open jobs, then latest posting.

**`company` block** (both modes; CSV/Excel exports flatten it to `company.*` columns):

| Field | Meaning |
|---|---|
| `match_status` | `matched`, `low_confidence` (a registry record is returned but its name does not agree with the board name: verify), `ambiguous` (several registry companies fit; see `candidate_tax_ids`), `not_found`, `skipped` |
| `match_method` | `domain_site` (the company's HTTP-verified website), `domain_email` (its registered corporate e-mail domain), `parent_domain_*`, `domain_name` (shared domain, the name singles one out), `domain_dominant` (shared domain, one company runs most establishments on it), `name_exact` (no website: exact legal/trade name held by exactly one company; only when the name index is published) |
| `match_confidence` | 0 to 1; `matched` means ≥ 0.6 |
| `match_note` | Why it matched or did not |
| `board_website`, `matched_domain`, `candidate_tax_ids` | Evidence used |
| `matched_establishment` | Brazil: when the evidence pointed to a branch (CNPJ ordem ≠ 0001), the block describes the **head office** of the same CNPJ root and this field keeps the branch (`tax_id`, `tax_id_formatted`, `city`, `region`) |
| `tax_id`, `tax_id_formatted`, `legal_name`, `trade_name`, `legal_form`, `is_headquarters`, `is_active`, `status` | Registry identity |
| `activity_code_local`, `activity_label_local` (CNAE), `activity_label_en`, `isic_*` | Activity |
| `size_bracket` (porte: ME, EPP, DEMAIS), `size_class`, `employees_bracket` | Size |
| `region`, `region_name`, `city`, `website`, `website_verified`, `registration_date` | Location and web |
| `data_as_of`, `source`, `source_license` | Registry release and official credit |

#### How the registry match works (and why it is honest)

1. The company's **own declared website** (Gupy career page, social-links section) is looked up against the registry's
   HTTP-verified websites and registered corporate e-mail domains (exact domain first, then the parent domain).
2. When several registry companies use that domain (corporate groups, dealers, accountants), the actor picks one only if
   it is the single one whose verified website it is, the single one whose name agrees with the board name, or the one
   running most establishments on the domain; otherwise the row is `ambiguous` with the candidates.
3. A registry name that shares nothing with the board name lowers the confidence below 0.6 (`low_confidence`):
   a dealer that registered the brand's domain is never sold as the brand.
4. Institutes, associations, funds and project companies (SPE) never win a name comparison unless the board name says so.
5. No tax ID is ever derived from a name without a score; masked (person-named) registry rows never match.
6. Without a usable website, the exact legal or trade name is looked up in a name index (employer legal forms only:
   no non-profits, condominiums, funds or public bodies) and accepted only when one company holds it (`name_exact`,
   0.6; one-word names stay `low_confidence`).
7. A Brazilian match on a branch returns the head office of the same CNPJ root (`matched_establishment` keeps the
   branch).

Measured on fresh samples (2026-11-04), with the name index: **Gupy** 438 postings / 249 companies: **41% of
companies (101) and 44% of postings match with confidence ≥ 0.6** (domain only: 35% / 40%); a hand check of 40
random matches found 39 right and 1 doubtful. **Computrabajo** 550 postings: 59% confidential; of the named ones
29% of postings (30% of companies) match by exact name; hand check 40/40 consistent. **InfoJobs** 250 postings: 53%
confidential; 50% of named companies match (domain from the company page, else name), 29/30 right. **Catho** 250: 22%
confidential, 29% of named companies match by name, 30/30. **Bumeran** 300 (100 per country): 11% confidential;
27% (AR), 39% (PE), 3% (MX) of named companies match by name, 30/30. **OCC** 250: 13% confidential, 37% of named
companies match by name, 29/30. Large groups that register their CNPJs with an accountant's e-mail are the main miss.

### Pricing

Pay per event, charged per output row:

| Event | When | Price |
|---|---|---|
| `job-result` | one posting row (`jobs` mode), whatever the match status | **US$0.002** |
| `company-result` | one hiring-company row (`hiring_companies` mode) | **US$0.008** |

1,000 postings cost US$2.00; 300 hiring companies cost US$2.40. Set a maximum cost per run and the actor stops when
it is reached (`OUTPUT.stopped_by_budget`).

**Never charged for a broken run.** If a board blocks the run or changed its layout (its first page cannot be read
and is not the board's own "no results" page), `OUTPUT.boards` says so per board; when that leaves the run without any
posting, the run **fails** with the board's name and nothing is charged. With several boards, the others are delivered
and the status message carries a warning. With `enrich: true` (default) and the company registry unreachable, the run
fails before reading any board; rows without the registry match are only returned when you set `enrich: false`.
In `jobs` mode rows are pushed as they are ready (the first ones after the first search page), and both modes end the
scan early near the run timeout, so a long run delivers what it found.

### Sources and attribution

Postings: Gupy public job portal (portal.gupy.io) and the companies' public career pages, and the public listing
pages of InfoJobs Brasil, Catho, Computrabajo, Bumeran and OCC Mundial; each row links to the posting (`url`). Registry: Receita Federal do Brasil, Dados Abertos CNPJ, credited in `company.source` /
`company.source_license`; keep them when you publish the data.

### Limitations

- Gupy's search returns at most 10,000 postings per query (offset window): split wide searches by keyword, state or
  workplace type.
- **Computrabajo**: list pages only (title, company, city/state, workplace, salary when published, date from the
  relative label, so `published_at` is approximate); no description, contract type or department. About **66% of
  Computrabajo postings are confidential** ("Importante empresa del sector"): `company_name` is null, the company
  block says so, and they are left out of `hiring_companies`. Computrabajo shows no company website, so its registry
  match is by exact name only and stays off until the name index is published (`match_note` says so).
- **InfoJobs Brasil**: list cards + one company page per company (declared website, size, sector); about half of the
  postings are confidential. **Catho**: list cards only; the city is shown without the state, so `state` filters do
  not apply; dates are "published" or "updated" days. **Bumeran**: the board's own search API (100 postings per
  request); no salary; Mexico rarely matches (DENUE names establishments). **OCC Mundial**: list pages only (offer
  pages are never requested); no description or workplace type.
- Employers whose name is a private person's name (sole traders) are returned like confidential postings: no name,
  no match (`match_note` says so).
- Mexico's registry (INEGI DENUE) publishes no RFC: for Mexican matches `tax_id` is the DENUE establishment id.
- `seniority` comes from the title; `salary_*` only when the posting labels a salary (about 2% of Gupy postings).
- Registry match needs the company's website; companies without one stay `not_found` unless the name index is enabled.
- Posting text is the company's own; descriptions are truncated to 600 characters.

### Privacy

Company data only. No recruiter or candidate data is collected; e-mail addresses, phone numbers and CPF numbers are
removed from descriptions before output. You are responsible for lawful use (LGPD) and for honouring opt-out requests
(hello@latsouth.com). LatSouth, São Paulo, Brazil.

### Related

[Brazil Company Finder](https://apify.com/latsouth/brazil-company-finder) (search companies by CNAE, city, size) ·
[LATAM Company Enrichment](https://apify.com/latsouth/latam-company-enrichment) (tax ID / domain lookup in 7
countries) · data files: <https://latsouth.com/products/>

### Português

#### Resumo

Vagas da **Gupy** (maior ATS do Brasil, ~80 mil vagas abertas) normalizadas e **cruzadas com o cadastro oficial de
CNPJ**: CNPJ, razão social, porte, CNAE, site verificado, com **método de match e nota de confiança**. No modo
`hiring_companies`, uma linha por empresa contratando (vagas abertas, áreas, senioridade, locais, vaga mais recente):
**sinal de intenção de contratação** para prospecção B2B. Também InfoJobs Brasil e Catho (Brasil), Computrabajo (México, Colômbia, Argentina, Peru, Chile), Bumeran (Argentina, Peru, México) e OCC Mundial (México), cada um cruzado com o registro do país; vagas confidenciais saem sem nome e sem match.

#### Exemplos

```json
{"keywords": ["analista de dados"], "workplace_types": ["remote"], "posted_within_days": 7, "max_results": 200}
```

```json
{"mode": "hiring_companies", "keywords": ["vendas", "sdr"], "state": "SP", "max_jobs_scanned": 2000, "max_results": 300}
```

#### Campos

- Vaga: título, empresa (como aparece na Gupy), site declarado pela empresa, cidade/UF, modelo (remoto, híbrido,
  presencial), tipo de contrato (CLT, PJ, estágio, aprendiz, temporário...), senioridade (pelo título), departamento,
  data de publicação, prazo, salário (só quando a vaga informa), descrição curta sem e-mails/telefones.
- Bloco `company`: `match_status` (`matched`, `low_confidence`, `ambiguous`, `not_found`), `match_method`,
  `match_confidence` (0 a 1), CNPJ formatado, razão social, nome fantasia, porte, CNAE, situação, cidade, site
  verificado, data do cadastro, fonte (Receita Federal).

#### Como o match funciona

Pelo **site que a própria empresa declara** na página de carreiras, comparado aos sites verificados e domínios de
e-mail corporativo do cadastro. Domínio compartilhado por várias empresas só vira match se uma delas for a dona do site
verificado, se o nome bater, ou se ela concentrar a maioria dos estabelecimentos; senão `ambiguous` com os candidatos.
Nome que não bate derruba a confiança (`low_confidence`). Sem site, nome exato
(só formas de empregador) quando uma única empresa o tem. Match em filial devolve a matriz da mesma raiz CNPJ
(`matched_establishment` guarda a filial). Medido em 438 vagas Gupy (249 empresas): **41% das empresas e 44% das
vagas com match ≥ 0,6** (revisão manual: 39 de 40 corretos).

#### Preço

**US$0,002 por vaga** (`job-result`) e **US$0,008 por empresa** (`company-result`). Defina um custo máximo por execução
e o actor para ao atingi-lo. Board bloqueado ou com layout alterado sem nenhuma vaga lida = execução **falha** com o nome
do board e nada é cobrado; cadastro de empresas fora do ar com `enrich: true` = falha antes de ler os boards.

#### Privacidade e LGPD

Só dados de empresa. Nenhum dado de recrutador ou candidato; e-mails, telefones e CPFs são removidos das descrições.
Uso conforme a LGPD é responsabilidade do usuário; pedidos de exclusão: hello@latsouth.com.

# Actor input Schema

## `mode` (type: `string`):

`jobs`: each posting with the hiring company's registry record. `hiring_companies`: postings grouped by company (number of open jobs, areas, seniority, locations, latest posting) = hiring-intent signal.

## `keywords` (type: `array`):

One search per keyword (Gupy searches job titles), results deduplicated. Empty = newest postings of the whole board.

## `sources` (type: `array`):

Gupy: Brazil's largest ATS portal (~80k open postings), with the company's declared website (registry match by domain). Computrabajo: Mexico, Colombia, Argentina, Peru, Chile (registry match by exact name). InfoJobs Brazil (the company page's declared website, registry match by domain, else by name). Catho Brazil (registry match by exact name). OCC Mundial Mexico (registry match by exact name). Bumeran: Argentina, Peru, Mexico (registry match by exact name). No keyword = newest postings of every board.

## `workplace_types` (type: `array`):

Empty = all.

## `state` (type: `string`):

UF code or name, e.g. SP or São Paulo.

## `city` (type: `string`):

e.g. Campinas.

## `posted_within_days` (type: `integer`):

Only postings published in the last N days. Empty = any date.

## `max_results` (type: `integer`):

Postings (jobs mode) or companies (hiring_companies mode) to return. You pay per row. Default 25: a first run costs about US$0.05 and every posting already carries the hiring company's registry record (`company`: tax id, legal name, size, activity).

## `max_jobs_scanned` (type: `integer`):

How many postings to read before grouping them by company. Scanning is free; you pay per company row.

## `enrich` (type: `boolean`):

Join each company to the official registry (CNPJ, legal name, size, CNAE activity, verified website) with match method and confidence.

## `include_description` (type: `boolean`):

First 600 characters, HTML removed, e-mails and phone numbers scrubbed.

## `proxyConfiguration` (type: `object`):

Off by default (direct requests). Optional Apify Proxy, datacenter only (a RESIDENTIAL group or custom proxy URLs are rejected: only Apify datacenter proxy is supported), only to choose the IP location and spread the request rate. Same headers and the same limit of 2 requests per second either way; it does not solve captchas or bot challenges: a board that blocks the run is reported as failed, never worked around.

## Actor input object example

```json
{
  "mode": "jobs",
  "keywords": [
    "analista de dados"
  ],
  "sources": [
    "gupy"
  ],
  "max_results": 25,
  "max_jobs_scanned": 1000,
  "enrich": true,
  "include_description": true,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `rows` (type: `string`):

Download as JSON, CSV, Excel or XML via the dataset API (CSV flattens `company.*`).

## `summary` (type: `string`):

Pages and postings read per board, companies, enrichment status counts, timings, HTTP requests and whether the run stopped at the buyer's budget cap.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "keywords": [
        "analista de dados"
    ],
    "max_results": 25
};

// Run the Actor and wait for it to finish
const run = await client.actor("latsouth/latam-jobs-hiring-signals").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "keywords": ["analista de dados"],
    "max_results": 25,
}

# Run the Actor and wait for it to finish
run = client.actor("latsouth/latam-jobs-hiring-signals").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "keywords": [
    "analista de dados"
  ],
  "max_results": 25
}' |
apify call latsouth/latam-jobs-hiring-signals --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,latsouth/latam-jobs-hiring-signals"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/7y9fwAHypFvGlXYM9/builds/bZrgMkLJlhlS8Y9zR/openapi.json
