# Catho Jobs Scraper - Brazil Vagas, Salaries & Details (`dami_studio/catho-jobs-scraper`) Actor

Every Catho job for your search words, as rows: title, company, city and state, the salary Catho shows, contract type, benefits, the full description and the posting date. Narrow it to a city, a state or Catho’s own home-office filter. No Catho account and no cookies. You pay per job delivered.

- **URL**: https://apify.com/dami\_studio/catho-jobs-scraper.md
- **Developed by:** [Dami's Studio](https://apify.com/dami_studio) (community)
- **Categories:** Jobs, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.40 / 1,000 job returneds

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

## Catho Jobs Scraper

Catho is where most of Brazil's hiring is posted, and its search box is not built for anyone who wants the
whole list. You get twenty cards at a time, no export, and the description only after a click.

This actor gives you the list. Type what you would type into Catho: `analista de dados`,
`tecnico de enfermagem`, `motorista`. Add a city or a state if you want one. Every matching job comes
back as a row: title, company, where it is, what it pays, the contract, the benefits, the full description,
and when it went up.

No Catho account. No cookies to paste. Nothing to log into.

***

### What a row looks like

A real one, copied out of a run on 20 September 2026. Only the description is cut short. It runs to 822
characters:

```json
{
  "jobId": "38432753",
  "title": "Enfermeiro para Cirurgia Plástica",
  "company": "Cirurgia Plástica",
  "confidentialCompany": false,
  "city": "São Paulo",
  "state": "SP",
  "stateName": "São Paulo",
  "country": "Brasil",
  "otherLocations": [],
  "seniority": "Pleno",
  "workModel": null,
  "homeOffice": null,
  "salaryText": "R$ 6.500",
  "salaryMin": 6500,
  "salaryMax": null,
  "salaryBand": "A partir de R$ 6.000,00",
  "salaryCurrency": "BRL",
  "contractType": "CLT (Efetivo)",
  "contractTypes": ["CLT (Efetivo)"],
  "workSchedule": "2a a sábado",
  "benefits": ["Seguro saúde", "Assistência médica / Medicina em grupo", "Assistência odontológica", "Vale-transporte"],
  "category": "Saúde",
  "subCategory": "Enfermagem",
  "roles": ["Enfermeiro de Centro Cirúrgico"],
  "openings": 1,
  "sponsored": true,
  "hasScreeningQuestions": true,
  "screeningQuestionCount": 4,
  "description": "Orientar e preparar o paciente no pré-operatório\nConferir exames, documentos e protocolos cirúrgicos\nAuxiliar a equipe m…",
  "postedAt": "2026-09-16T10:47:24",
  "postedText": "Publicada em 16/09",
  "updatedAt": "2026-09-16T10:47:24",
  "expiresAt": "2026-12-15T10:47:24",
  "url": "https://www.catho.com.br/vagas/enfermeiro-para-cirurgia-plastica/38432753",
  "searchTerm": "enfermeiro",
  "detailsLoaded": true,
  "scrapedAt": "2026-09-20T00:33:14.368Z"
}
```

That is a well-filled row. Plenty are thinner. `workSchedule`, `benefits` and `salaryMin` are empty more
often than not, and the table further down says how often.

***

### Input

```json
{
  "searches": ["analista de dados", "cientista de dados"],
  "city": "São Paulo, SP",
  "workModel": "remote",
  "sortBy": "recent",
  "maxResults": 200,
  "includeDescription": true
}
```

| Field | What it does |
|---|---|
| `searches` | Up to 10 lines. Each is its own Catho search. Accents optional. |
| `city` | `"São Paulo, SP"`, **with the two-letter state.** Catho has more than one city of most names, and picks a small one if you leave the state off. This actor refuses a bare city name rather than quietly returning the wrong town. |
| `state` | Used on its own for a whole state, or alongside a city written without one. |
| `workModel` | `remote`, `hybrid`, `presential`, or any. This is Catho's own Modalidade filter. There is a note below on why it matters. |
| `sortBy` | `recent` for newest first, or Catho's relevance order. |
| `maxResults` | Across all your searches. Each job returned is charged once. |
| `includeDescription` | On by default. Off returns only what the search card shows and reads about six times less. |

Run it with no `searches` at all and you get one example row, marked `_sample`, free, so you can see the
shape before you commit to anything.

***

### The one thing to know about "home office"

Catho does not put a work model on the job. Not on the card, not in the job's own record, not in the page
markup. We looked in all three. The only place it exists is Catho's own **Modalidade** filter.

So: set `workModel` to `remote` and every row you get back is a job Catho itself files as home office, with
`workModel: "remote"` and `homeOffice: true` on it. Leave it on Any and those two fields come back `null`,
because guessing from the description would be making it up. A job whose text says "Home Office" in the
middle of a paragraph is not the same thing as a job Catho has flagged, and we are not going to pretend it
is.

If you want a home-office feed, filter for it. That is the honest way to get one here.

***

### What the salary field actually contains

Brazilian employers on Catho mostly publish a **band**, not a number. Measured across 120 jobs from a broad
nursing search and 39 from filtered developer searches:

- `salaryBand`, filled on **100%** of rows. Catho's own wording: `"A Combinar"`, `"A partir de R$ 2.000,00"`,
  `"Até R$ 2.000,00"`.
- `salaryMin`, an actual figure on **26%** of rows. When the employer published one, it is there.

Nothing is hidden behind a login. Catho shows the same salary to a signed-out visitor as to a signed-in one,
and that is what you get. There just often isn't a number to show.

***

### Coverage, measured

| Field | Broad search (120 jobs) | Filtered search (39 jobs) |
|---|---|---|
| title, city, state, description, postedAt, url, salaryBand | 100% | 100% |
| company named | 85% | 90% |
| contractType | 97% | 100% |
| category | 98% | 97% |
| benefits | 51% | 62% |
| salaryMin (a real figure) | 26% | 10% |
| seniority | 20% | 54% |

Where `company` is empty, `confidentialCompany` is `true`. That is Catho's "Empresa Confidencial", where
the employer chose not to be named. The row is still complete in every other way.

***

### What this does not do

- **It does not invent a work model.** See above. `null` where Catho says nothing.
- **`seniority` is read off the job title**, because Catho has no seniority field. `Júnior`, `Pleno`,
  `Sênior`, `Estágio`, `Trainee`, `Coordenação`, `Gerência` and so on, when the employer put one there.
  About a fifth of jobs on a broad search, half on developer searches. It is `null` otherwise, not
  defaulted to something.
- **Catho's results count is not the number of jobs you can get.** Its page says "8.782 resultados" for
  `desenvolvedor`; paging through it end to end gives **763** distinct jobs. That is Catho's limit, not
  this actor's, and the actor stops when Catho stops rather than paying for a page of repeats. Depth
  varies by term: `enfermeiro` was still going past 880.
- **No filters beyond the ones listed.** Catho's contract, salary-band and posting-date filters have no
  working URL form. We tried fourteen shapes and Catho ignored every one. Rather than ship a control that
  silently does nothing, they are not here. Filter `contractType` and `postedAt` on the rows instead.
- **No résumés, no candidates, no employer contact details.** Job listings only.
- **No Catho account**, so nothing that needs one.

***

### Pricing

You pay per job delivered, flat, with no volume tiers. The price on this page is the price at every size.

Free, always:

- the `_sample` row an empty run returns;
- a search that finds nothing;
- a job already delivered by an earlier search in the same run. Repeats are dropped, not billed;
- a job whose full record did not load when you asked for details;
- a search where Catho did not recognise the city you gave (you get a row saying so, free, instead of a
  nationwide crawl you did not ask for).

If you set a maximum charge on the run, the actor reads it before its first request and stops at it.

***

### Speed, from real runs

| Run | Jobs | Time |
|---|---|---|
| one search, one city, newest first | 12 | 3.0 s |
| three searches, one city, home office only | 39 | 5.7 s |
| one search, nationwide | 120 | 8.6 s |

***

### Questions people actually ask

**Can I scrape Catho without logging in?**
Yes. This actor never signs in and never handles a cookie. Everything it returns is what Catho shows a
signed-out visitor.

**Can I get the salary from Catho jobs?**
You get whatever Catho publishes: the band on every job, and a figure on about a quarter of them. Nothing is
withheld from signed-out visitors that signing in would reveal.

**How do I get only home-office jobs from Catho?**
Set `workModel` to `remote`. That is Catho's own Modalidade filter, and it is the only place Catho records a
job's work model.

**How many jobs can I pull for one search term?**
As many as Catho will page. Measured: 763 for `desenvolvedor`, over 880 for `enfermeiro`. The number Catho
prints above the list is much larger than what it will actually serve.

**Can I run this every morning for new postings?**
Yes. Set `sortBy` to `recent` and a modest `maxResults`. Each row carries `postedAt` and `jobId`, so
de-duplicating against yesterday's pull is a one-line join.

**What comes back if a term has no jobs?**
Nothing, and nothing is charged. The run still finishes green and the status message says which search came
up empty.

**Does it work for terms in English?**
Where Brazilian employers post in English, yes. `software engineer` returns real listings. Portuguese
terms return far more.

***

### The run report

Every run writes a `RUN_REPORT` record next to the data: per search, how many jobs Catho claimed, how many
pages were read, how many came back, how many repeats were skipped, how many had no details, and why the
search stopped. Worth a look when a number surprises you.

# Actor input Schema

## `searches` (type: `array`):

Job titles, roles or skills, one per line, up to 10. Each line is its own search on Catho, written the way you would type it into Catho's own box: desenvolvedor, analista de dados, tecnico de enfermagem. Accents are optional. English words work where Catho's employers use them.

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

Only jobs in this city, with its two-letter state: "São Paulo, SP". The state matters — Catho has more than one city of most names and picks a small one otherwise. Leave it empty for a whole state, or for the whole country.

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

Only jobs in this state. Used on its own for a whole state, or together with a city that has no state written on it.

## `workModel` (type: `string`):

Catho's own Modalidade filter. This is the only place Catho publishes a job's work model — it is not on the job card, not in the job's own data and not in its page markup — so rows carry a work model only when you pick one here. Leave it on Any and the workModel and homeOffice fields come back empty rather than guessed.

## `sortBy` (type: `string`):

Catho's own two orders. Most recent first is what you want for a daily feed; Catho's relevance order mixes older jobs in.

## `maxResults` (type: `integer`):

The most jobs returned in one run, across all your searches. Each job returned is charged once. Catho pages about 20 jobs at a time and most searches run dry somewhere between 300 and 900 jobs, whatever the results count at the top of its page claims.

## `includeDescription` (type: `boolean`):

Opens each job's own record for the full description, the state, contract type, benefits, the exact salary when the employer published one, Catho's salary band, the area it is filed under and the posting date. Switch it off and only what the search card shows comes back — title, company, city, the salary line and a rough posting tag — which reads about six times less.

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

Optional. Leave this alone unless you need the run to go out through a particular network. Your own proxy servers are used exactly as given.

## Actor input object example

```json
{
  "searches": [
    "analista de dados"
  ],
  "state": "ANY",
  "workModel": "ANY",
  "sortBy": "relevance",
  "maxResults": 100,
  "includeDescription": true,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `results` (type: `string`):

One row per job: title, company where Catho names it, city and state, the salary line and Catho's own salary band, contract type, benefits, the area it is filed under, openings, the full description, the posting date and the link. A run with no search words returns one sample job, marked \_sample, not charged.

## `report` (type: `string`):

For each search: how many jobs Catho claimed, how many pages were read, how many jobs came back, how many repeats were skipped, how many had no details, and why the search stopped.

# 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 = {
    "searches": [
        "analista de dados"
    ],
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("dami_studio/catho-jobs-scraper").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 = {
    "searches": ["analista de dados"],
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("dami_studio/catho-jobs-scraper").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 '{
  "searches": [
    "analista de dados"
  ],
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call dami_studio/catho-jobs-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,dami_studio/catho-jobs-scraper"
        }
    }
}
```

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/dRTpAveozmxsumB9c/builds/yB2sq8Ve39edJpGXm/openapi.json
