# Gupy Jobs Scraper - Jobs, Companies, Locations & Deadlines (`abotapi/gupy-io-scraper`) Actor

Scrape public jobs from Gupy.io by keyword, state, city, workplace type, job type, company, PWD, and feedback badge. Extract rich job details, company information, locations, application deadlines, and badge data.

- **URL**: https://apify.com/abotapi/gupy-io-scraper.md
- **Developed by:** [Abot API](https://apify.com/abotapi) (community)
- **Categories:** Jobs, Lead generation, Automation
- **Features:** Limited permissions
- **Stats:** 3 total users, 1 monthly users, 73.5% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 job results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## Gupy.io Jobs Scraper: Job Listings, Companies & Application Deadlines

Gupy.io Jobs Scraper turns Gupy.io, a Brazilian job board and recruitment platform, into a clean jobs data feed. Search by keyword, Brazilian state, city, workplace type, job type, exact company career page, PWD accessible jobs, or feedback badge companies, or paste portal search links and direct Gupy job links. Turn on full detail enrichment for the complete per job record, sort results newest, oldest, or by company, then export to JSON, CSV or Excel, or pull the results straight into your app through the API.

### Why This Scraper?

- **Two ways to find jobs.** Run a filtered search (keyword, state, city, workplace type, job type, company, PWD only, feedback badge only) or paste portal search links and direct Gupy job links. Direct job links are always fetched in full, whatever the detail toggle is set to.
- **Optional full detail enrichment.** Turn on Fetch details to add the complete per job record. The detail enrichment charge only applies to a job that actually reaches the dataset, not to every detail page fetched. Jobs from direct Gupy job links are always fully fetched, so they carry the detail enrichment charge even with Fetch details off.
- **Sort after collection.** Order saved jobs newest, oldest, or by company name, A to Z.
- **Full catalogue pagination by default.** The walk covers every result page unless you cap it, and leading pages that were all collected before are skipped past rather than ending the walk. See the pagination notes below for the cases where a walk can still end early.
- **Survives interruptions.** A run that gets migrated or restarted mid walk reloads its already collected jobs and ids, so they are not saved twice, detail fetched again, or charged again.
- **Resume across separate runs.** Paste a previous run or dataset ID to keep pulling a large catalogue over several runs without re saving jobs you already have.
- **Built for schedules.** Incremental mode classifies every job as NEW, UPDATED, UNCHANGED, REAPPEARED, or EXPIRED against the last run of the same search, so a recurring schedule returns only what changed.

### Use Cases

- **Recruiters and talent teams:** watch a set of companies' Gupy career pages for new openings by keyword, state, or workplace type.
- **Job boards and aggregators:** mirror Gupy listings for a state, city, or workplace type into your own catalogue.
- **Workforce and HR analytics:** run the same search on a schedule with Incremental mode and track how postings and requirements change over time.
- **Accessibility focused services:** filter for PWD marked listings across many companies at once.
- **Automation and agent builders:** pipe new listings straight into Notion, Linear, or Airtable through the MCP connector field.

### Data You Get

> Sample shape: values are illustrative placeholders, not from a live record.

| Field | Example |
| --- | --- |
| `id` | `3050001` |
| `name` | `"Analista de Sistemas Pleno"` |
| `jobUrl` | `"https://sample-company.gupy.io/job/eyJqb2JJZCI6MzA1MDAwMX0="` |
| `description` | `"<p>Full HTML job description as published.</p>"` |
| `descriptionPlainText` | `"Full job description, converted to plain text."` |
| `city` | `"São Paulo"` |
| `state` | `"São Paulo"` |
| `stateCode` | `"SP"` |
| `country` | `"Brasil"` (filled when the job's state is Brazilian, or always with **Fetch details** on; empty when the listing does not say, since jobs from other Latin American countries are listed too) |
| `jobType` | `"vacancy_type_effective"` |
| `workplaceType` | `"hybrid"` |
| `isRemoteWork` | `false` |
| `publishedAt` | `"2026-08-01T12:00:00.000Z"` |
| `applicationDeadline` | `"2026-09-15T23:59:59.000Z"` |
| `expireDays` | `17` |
| `careerPageName` | `"Sample Company"` |
| `careerPageDisplayName` | `"Sample Company"` (same value as `careerPageName`) |
| `careerPageUrl` | `"https://sample-company.gupy.io"` |
| `hasFriendlyBadge` | `true` |
| `badgeIsPWD` | `false` |
| `skills` | `["Comunicação", "Excel"]` |
| `detailFetched` | `true` (only when Fetch details was on, or the job came from a direct job link) |
| `source` | the complete source record as returned by the API |
| `scrapedAt` | `"2026-09-29T09:00:00.000Z"` |

With the full record fetched, each job also carries `applicationQuestions`, `requiredApplicationFields`, `companyIntegrations` and `relevantExperiences` (otherwise `null`).

Every record also carries a long list of schema compatibility fields shared with other job board actors. `benefits`, `addressComplements`, `addressDistrict` and `jobRatingCriterias` are not published in Gupy's public data, so they are always `null`. The company social links (`companyLinkedin`, `companyFacebook`, `companyTwitter`, `companyInstagram`, `companyGithub`, `companyGlassdoor`), the job images (`jobImage`, `jobSocialImage`, `jobSmartphoneImage`), the video (`videoUrl`, `videoSectionTitle`), `careerPageWebsite`, `careerPageKnowMoreTitle` and `prerequisites` are filled when Fetch details is on and the company publishes them, and are otherwise `null`.

### How to Use

1. Pick a **mode**: `search` (use the filters below) or `url` (paste portal search links or direct Gupy job links).
2. In search mode, set the keyword, state, city, workplace type, job type, company, PWD, and feedback badge filters you need, and turn on Fetch details if you want the full per job record.
3. Set **Max items** (and Max pages or Page size if needed) to control run size and cost, then click **Start**.
4. Download the dataset as JSON, CSV or Excel, or read it through the API.

**Search by keyword, state, and workplace type:**

```json
{
  "searchTerm": "developer",
  "state": "São Paulo",
  "workplaceTypes": ["remote"],
  "sortBy": "newest",
  "maxItems": 20
}
```

**One company's career page, with full detail per job:**

```json
{
  "companies": ["Sample Company"],
  "fetchDetails": true,
  "maxItems": 50
}
```

**Supported link types (URL mode):**

| Link type | Example |
|---|---|
| Portal search, term in the path | `https://portal.gupy.io/job-search/term=developer` |
| Portal search with filters (state, workplaceTypes\[]) | `https://portal.gupy.io/job-search/term=developer&state=S%C3%A3o%20Paulo&workplaceTypes[]=remote` |
| Portal search with sortBy and page (starts at that page of 10; a partial scan, so never produces `EXPIRED`) | `https://portal.gupy.io/job-search/term=developer&sortBy=publishedDate&page=2` |
| Company career page (lists that company's jobs) | `https://tfcarreira.gupy.io/` |
| Portal search by location only (state and city, no term) | `https://portal.gupy.io/job-search/state=S%C3%A3o%20Paulo&city=S%C3%A3o%20Paulo` |
| Portal search filtered to one company | `https://portal.gupy.io/job-search/careerPageName[]=FARM` |
| Job detail with the base64 job token | `https://tfcarreira.gupy.io/job/eyJqb2JJZCI6MTI3NDg4MzIsInNvdXJjZSI6Imd1cHlfcG9ydGFsIn0=?jobBoardSource=gupy_portal` |

Any other company-site link (for example a bare numeric `/job/12748832`) is skipped with a warning in the log instead of silently widening to all jobs.

**Behaviour change:** URL mode with the `urls` field left out now scrapes these 7 examples instead of nothing. Companies and cities typed by hand are matched ignoring case and accents (company names also by part of the name); a name that matches nothing fails the run with a clear message.

**Paste links (portal search and a direct job page):**

```json
{
  "mode": "url",
  "urls": [
    "https://portal.gupy.io/job-search/term=developer&state=Bahia",
    "https://sample-company.gupy.io/job/eyJqb2JJZCI6MzA1MDAwMX0="
  ],
  "maxItems": 10
}
```

**Recurring monitoring of the same search, including jobs that disappear:**

```json
{
  "searchTerm": "developer",
  "incrementalMode": true,
  "emitExpired": true,
  "maxItems": 0
}
```

#### Run it from your code

Python:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("abotapi/gupy-io-scraper").call(run_input={"searchTerm": "developer", "state": "São Paulo"})
for job in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(job["name"], job["careerPageDisplayName"])
```

JavaScript:

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

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('abotapi/gupy-io-scraper').call({ searchTerm: 'developer', state: 'São Paulo' });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

Or connect it to Make, Zapier, n8n, Google Sheets or webhooks from the **Integrations** tab.

#### How Max items and Max pages shape a run

**Max items** is a soft cap on saved rows (default 20, `0` means unlimited). **Max pages** defaults to `0`, meaning the walk covers every result page; a positive number stops the walk after that many API pages, whichever limit is reached first. Gupy's own reported result total is not a reliable stop signal once a search has many matches, so the walk instead relies on a page coming back empty, coming back shorter than requested, or containing no job the run has not already collected (after earlier pages of that same search did add new jobs). Fewer rows than Max items may still come back when the chosen source and filters simply match fewer live jobs.

**A known limitation of that last stop rule.** "Already collected" is judged against everything this run holds, not just the current search: jobs from earlier links in the same run, and jobs reloaded by Resume. So a search can stop before its real last page, with no error, in two cases:

- **Several overlapping search links in one run (URL mode).** If a later link finds some new jobs, then hits a full page of jobs an earlier link already returned, that later link stops there and its deeper pages are skipped. Search mode builds a single search and is not affected by this case. Run overlapping links as separate runs.
- **Resume after new jobs were posted.** When `resumeFromRunId` (or an automatic restart after a platform migration) replays a search whose first pages now hold a few newly posted jobs ahead of the ones already collected, the next page of already collected jobs ends the walk, so jobs beyond the earlier stopping point are not reached. If a resumed run returns far fewer jobs than expected, run the search again without Resume instead.

#### Resume and recurring updates

- **Resume** (`resumeFromRunId`) continues a catalogue walk from an earlier run that finished and saved its jobs, for example one stopped by Max items: paste its run or dataset ID and jobs already collected there (matched by job ID) are skipped, so this run only appends new ones. Jobs are written to the dataset only when a run finishes, so a run that was aborted or failed partway has nothing to resume from; resurrect that run instead, and it reloads its own saved progress. An unreadable run or dataset ID fails the run immediately, before any scraping starts.
- **Incremental mode** (`incrementalMode`) is for scheduling this same search again and again. Each run classifies every job against the last run's state for that search (kept automatically, or under your own `stateKey`) and adds a `changeType`: `NEW` (not seen before), `UPDATED` (a field changed, listed in `changedFields`), `UNCHANGED` (nothing changed, suppressed unless `emitUnchanged` is on), `REAPPEARED` (was marked EXPIRED, found again), or `EXPIRED` (tracked before, not found in a run that fully scanned this search, only produced when `emitExpired` is on). A job counts as `UPDATED` when any saved field differs from the last tracked run; `scrapedAt` and `expireDays` (a day count that drifts daily) are always left out of the comparison, and you can add more with `ignoreFieldsForChanges`. Ignored fields stay in the output. Nested fields use dotted paths such as `source.salary`. The first incremental run after this release does not mark tracked jobs `UPDATED` just because the new fields appeared (`applicationQuestions`, `requiredApplicationFields`, `companyIntegrations`, `relevantExperiences`), because company, career page, image, video and state code fields that used to be empty are now filled when the full record is fetched, or because `sourceQuery` now records the start page of a pasted `page=N` link and the career page of a pasted company link (`https://<company>.gupy.io/`, which is now scoped to that company instead of searching all jobs). After that first run, a real change in any of them counts as `UPDATED` as usual. A pasted single job link is a partial scan, so it never produces `EXPIRED`. The first complete run of a tracked company career page link after this release does not mark the unrelated jobs that the earlier unscoped search collected as `EXPIRED` (they are outside the new scope, so they are dropped from the saved state quietly and are not saved or billed); use a fresh `stateKey` if you prefer a clean start. Every job also carries `firstSeenAt` and `lastSeenAt`. `EXPIRED` is only ever computed when the run reaches the natural end of the walk; a run capped by Max items or Max pages, or one started with `resumeFromRunId`, skips it for that run and keeps the previous state as is. One exception to watch for: an early stop caused by overlapping search links (described above) still counts as reaching the natural end, so with `emitExpired` on, a URL mode run with several overlapping links can mark still live jobs `EXPIRED`. Keep each tracked search to one link per run if you rely on `EXPIRED`. Turning `incrementalMode` on together with `resumeFromRunId` fails the run if that search already has saved incremental state; remove one of the two, or set a different `stateKey`. With Incremental mode off, output is exactly as before.

### Send results into your apps (MCP connectors)

Optionally pipe the scraped jobs into the apps you already use, via Model Context Protocol (MCP) connectors. This is an extra delivery step **after** the scrape: the Apify dataset is never changed.

**What gets written to the connector:** a condensed, human readable **summary** of each job, not the full JSON. Each job becomes one entry with a **title** and its key fields flattened to plain text. The **complete record always stays in the Apify dataset**.

1. Authorize a connector once under **Apify → Settings → Integrations** (Notion, Linear, Airtable, or Apify).
2. Select it in the **"Pipe results into your apps"** input field. (If the picker is empty, you haven't authorized a connector yet.)
3. For **Notion**, also set `notionParentPageUrl` to the page where job pages should be created.

The connection is mediated by Apify's MCP proxy, so this actor never sees your third party credentials. Leave the field empty to skip.

### Input Parameters

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `mode` | string | `search` | `search` (filters below) or `url` (portal search links and direct Gupy job links). |
| `searchTerm` | string | (empty) | Job title, skill, or keyword. Leave empty to scrape all jobs matching the other filters. |
| `state` | string | (empty) | Brazilian state filter. Leave empty for all states. |
| `cities` | array | `[]` | Gupy city names. Leave empty for all cities. |
| `workplaceTypes` | array | `[]` | Remote, hybrid, or on site jobs. Leave empty for all workplace types. |
| `jobTypes` | array | `[]` | Gupy vacancy type values (full time, apprentice, internship, trainee, and more). Leave empty for all job types. |
| `companies` | array | `[]` | Gupy career page names. Leave empty for all companies. |
| `pwdOnly` | boolean | `false` | Return only jobs marked for people with disabilities. |
| `friendlyBadgeOnly` | boolean | `false` | Return only jobs from companies with Gupy's feedback badge. |
| `urls` | array | the 7 example links below | Portal search URLs, company career pages or direct Gupy job URLs. Used only when Mode is `url`. If you leave the field out, the 7 examples run (one per supported link type); pass `[]` to run none. |
| `fetchDetails` | boolean | `false` | Fetch the full per job record before saving each result (search mode and portal search URLs only; direct job URLs are always fetched in full and always carry the detail enrichment charge). Adds the detail enrichment charge per enriched job actually saved. |
| `sortBy` | string | `newest` | Sort saved jobs after collection: `newest`, `oldest`, or `company` (A to Z). |
| `maxItems` | integer | `20` | Maximum matching rows to save. `0` means unlimited. Fewer rows may come back when the source and filters match fewer live jobs. |
| `maxPages` | integer | `0` | Leave at `0` to walk every result page. A positive number stops the walk after that many API pages. |
| `pageSize` | integer | `100` | API page size, from 1 to 100. |
| `resumeFromRunId` | string | (none) | A previous run ID or dataset ID to continue a full catalogue walk. Jobs already collected there are skipped. |
| `incrementalMode` | boolean | `false` | Track this search's results between scheduled runs and classify each job as NEW, UPDATED, UNCHANGED, REAPPEARED, or EXPIRED. |
| `stateKey` | string | (none) | Name an incremental mode baseline explicitly. Leave empty to derive one automatically from the search filters. |
| `emitUnchanged` | boolean | `false` | Incremental mode only. Also return (and bill) jobs unchanged since the last tracked run. |
| `emitExpired` | boolean | `false` | Incremental mode only. Also return (and bill) jobs tracked before but no longer found, after a run that fully scans the search. |
| `maxResults` | integer | (none) | Alias of `maxItems`; `maxItems` wins when both are set. |
| `feedback` | string | (none) | Optional free text note to the developers. Has no effect on the scrape. |
| `ignoreFieldsForChanges` | array | (none) | Incremental mode only. Field names (dotted paths for nested fields) that never count as a change: they stay in every row but cannot make a job UPDATED or show up in `changedFields`. |
| `mcpConnectors` | array | (none) | Optionally send the scraped jobs into the apps you already use, via MCP connectors. Leave empty to skip. |
| `notionParentPageUrl` | string | (none) | Notion connector only: page under which job pages are created. |
| `maxNotifyListings` | integer | `50` | Cap on jobs written to each connector per run. Does not affect the dataset. |
| `proxyConfiguration` | object | Apify Proxy | Connection settings, honoured on every run (see Plan Requirement). |

The dataset has three views in the Console: **Jobs overview**, **Job details** (full description, application questions and company data) and **Changes (incremental)** (`changeType`, `changedFields`, `firstSeenAt`, `lastSeenAt`).

### Output Example

> Sample shape: values are illustrative placeholders, not from a live record.

```json
{
  "id": 3050001,
  "code": "9001-3050001",
  "name": "Analista de Sistemas Pleno",
  "jobUrl": "https://sample-company.gupy.io/job/eyJqb2JJZCI6MzA1MDAwMX0=",
  "description": "<p>Full HTML job description as published.</p>",
  "descriptionPlainText": "Full job description, converted to plain text.",
  "prerequisites": null,
  "city": "São Paulo",
  "state": "São Paulo",
  "stateCode": "SP",
  "country": "Brasil",
  "countryCode": "BR",
  "jobType": "vacancy_type_effective",
  "workplaceType": "hybrid",
  "isRemoteWork": false,
  "publishedAt": "2026-08-01T12:00:00.000Z",
  "applicationDeadline": "2026-09-15T23:59:59.000Z",
  "expireDays": 17,
  "companyId": 9001,
  "companySubdomain": "sample-company",
  "careerPageName": "Sample Company",
  "careerPageDisplayName": "Sample Company",
  "careerPageUrl": "https://sample-company.gupy.io",
  "hasFriendlyBadge": true,
  "badgeIsPWD": false,
  "skills": ["Comunicação", "Excel"],
  "sourceQuery": { "searchTerm": "developer", "state": "São Paulo", "cities": [], "workplaceTypes": [], "jobTypes": [], "companies": [], "pwdOnly": false, "friendlyBadgeOnly": false },
  "detailFetched": true,
  "source": {},
  "scrapedAt": "2026-09-29T09:00:00.000Z"
}
```

### Plan Requirement

The default connection setting is Apify Proxy, and the Connection input is honoured on every run. The default Apify datacenter connection was measured serving Gupy: the portal search pages, company career pages, job pages and the job search data all loaded, so the default needs no residential proxy and no action on a plan that includes Apify Proxy. Apify free plans do not include any Apify proxy, so on a free plan the actor reports what happened in the run log and continues on a backup connection:

- If the selected connection is unavailable, the log shows "The selected connection is unavailable to this run. Apify free plans do not include residential proxies. Continuing on a backup connection; some pages may still be unavailable." Once the backup connection serves data it logs "Served over the backup connection (the default connection was unavailable)."
- If no results page can be loaded at all, the run fails instead of returning an empty dataset, with one of three messages: "The proxy connection was refused for this account, so no page could be read. Your Apify plan does not include proxy access; a paid plan is required" (also check your own proxy URLs if you entered any); "No results: residential proxies are unavailable on this run (Apify free plans do not include them), and the backup connection could not load the requested Gupy pages"; or "Could not load any results page (the requests failed), so nothing was collected. This is a connection problem, not an empty search. Please try again later."

If you hit these, upgrade your Apify plan or supply your own proxy URLs under Connection.

### FAQ

#### How much does it cost?

You pay per job saved, plus a one time actor start fee, with the detail enrichment charge added for each job whose full record was fetched and that actually reaches the dataset: every job when Fetch details is on, and always for jobs from direct Gupy job links. The **Pricing** tab shows the current rates. The detail enrichment charge is applied right after each batch of up to 50 saved jobs is written, only for the jobs in that batch whose full record was fetched, never for a job that was not saved. Events charged: the actor start (once per run), the saved job (per job) and the detail enrichment (per enriched job). Use **Max items** to cap the cost of any run.

#### Is it legal to scrape Gupy.io?

This actor collects only publicly available job listings. You are responsible for how you use it: follow Gupy.io's terms and the laws that apply to you, and get legal advice if you plan commercial redistribution.

#### Can I get only new or changed jobs on a schedule?

Yes. Schedule the actor from the **Schedules** tab and turn on **Incremental mode**. Each run then returns only NEW, UPDATED, and REAPPEARED jobs for that search, and unchanged ones are not billed unless you turn on **Emit unchanged**.

#### Why did my run return fewer jobs than I expected?

Max items and Max pages both cap a run, and Gupy's own reported result total is not a reliable signal once a search has many matches. Raise Max items, raise or clear Max pages, or set Max pages to `0`, to walk the whole result set. Two cases can also end a search early without an error: several overlapping search links in one URL mode run, and a Resume run after new jobs were posted. Both are described under "How Max items and Max pages shape a run" above.

#### Why did my run fail instead of returning an empty dataset?

Two situations fail the run outright rather than returning an empty dataset: pasting a Resume from a previous run ID that this Apify account cannot read (checked before any scraping starts), and turning on Incremental mode together with Resume from a previous run when that search already has saved incremental state. Fix the ID, or remove one of the two settings, and run it again. A run also fails when no results page could be loaded through any connection (see Plan Requirement), and when a company or city you typed matches nothing.

#### Can I use it with AI agents or MCP?

Yes. Call it from any Apify integration or MCP client, and use the connector field to push results into Notion, Linear, or Airtable.

### 🔗 Want more jobs data?

Pair this actor with these related scrapers from the same team:

<table>
<tr><td>💼 <a href="https://apify.com/abotapi/jora-jobs-scraper"><b>Jora Jobs Scraper</b></a><br>Scrape Jora.com job listings across supported countries by keyword, filters, or URL...</td><td>💼 <a href="https://apify.com/abotapi/myworkdayjobs-scraper"><b>Workday Jobs Scraper</b></a><br>Scrape job postings from any Workday-hosted careers site on Myworkdayjobs.com. Paste...</td></tr>
<tr><td>🏷️ <a href="https://apify.com/abotapi/finn-no-jobs-scraper"><b>FINN.no Jobs Scraper</b></a><br>Scrape active FINN.no job listings by search filters or URL. Extract 30+ fields including...</td><td>💼 <a href="https://apify.com/abotapi/jobsite-co-uk-scraper"><b>Jobsite UK</b></a><br>Collect UK job listings from Jobsite.co.uk at scale. Search by keyword, location...</td></tr>
<tr><td>💼 <a href="https://apify.com/abotapi/we-work-remotely-jobs-scraper"><b>We Work Remotely Scraper</b></a><br>Extract current remote job listings from weworkremotely.com. Use keyword search or WWR...</td><td>💼 <a href="https://apify.com/abotapi/edjoin-scraper"><b>EdJoin.org Scraper</b></a><br>Scrape EdJoin.org K-12 and higher-ed jobs by keyword, location, filters or URL. Extract...</td></tr>
</table>

👉 [Browse all abotapi scrapers](https://apify.com/abotapi)

### 💬 Support

- 🐞 **Found a bug or a missing field?** Open a ticket on the [Issues tab](https://apify.com/abotapi/gupy-io-scraper/issues/open). We usually reply within hours.
- 🛠️ **Need another site, extra fields or a private build?** Email <contact@abotapi.com>.
- ⭐ **Enjoying it?** A quick review on the actor page helps other users find it.

# Actor input Schema

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

Search mode uses the filters below. URL mode accepts portal search URLs and Gupy company job URLs.

## `searchTerm` (type: `string`):

Job title, skill, or keyword. Leave empty to scrape all jobs matching the other filters.

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

Brazilian state filter. Leave empty for all states.

## `cities` (type: `array`):

City names, for example 'Sao Paulo' (accents are restored). A city that matches nothing fails the run. Leave empty for all cities.

## `workplaceTypes` (type: `array`):

Remote, hybrid, or on-site jobs. Leave empty for all workplace types.

## `jobTypes` (type: `array`):

Gupy vacancy type values. Leave empty for all job types.

## `companies` (type: `array`):

Career page names, for example 'PagBank' or '#sejaveriter'. Matching ignores case and accents and accepts part of a name; a name that matches nothing fails the run. Leave empty for all companies.

## `pwdOnly` (type: `boolean`):

Return only jobs marked for people with disabilities.

## `friendlyBadgeOnly` (type: `boolean`):

Return only jobs from companies with Gupy feedback badge.

## `urls` (type: `array`):

Portal search URLs (term, state, workplaceTypes, sortBy, page), company career pages (https://<company>.gupy.io/) or Gupy job links. Used only when Mode is URLs. Leaving this field out runs the 7 examples.

## `fetchDetails` (type: `boolean`):

Fetch the full per-job record before saving each result. Applies to Search mode and to portal search URLs. Direct Gupy job URLs are always fetched in full, so this toggle does not affect them. Adds the detail-enrichment charge per enriched job that is actually saved — a job suppressed by Incremental mode (UNCHANGED, Emit unchanged off) is never charged even though its detail page was fetched. Also part of the Incremental mode baseline key: turning it on or off starts a separate tracked baseline.

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

Sort saved jobs after collection. Applies in both Search and URL mode.

## `maxItems` (type: `integer`):

Maximum matching rows to save. Fewer rows may be returned when the selected source, keyword, and filters match fewer live records.

## `maxPages` (type: `integer`):

Leave empty to walk every result page. Set 0 for unlimited; the run stops at Max items. A positive number caps the walk at that many pages.

## `pageSize` (type: `integer`):

API page size. Larger pages are faster; smaller pages are useful for debugging.

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

Works on every plan, including Free. Apify Proxy is recommended for reliability and is the default; if you see repeated source errors, enable it.

## `resumeFromRunId` (type: `string`):

Paste a previous run ID or dataset ID to continue a full-catalogue walk across separate runs. Jobs already collected there (matched by job id) are skipped, so this run only saves the new ones. Leave empty for a normal fresh run. For recurring monitoring of the same search instead, use Incremental & dedup mode below.

## `incrementalMode` (type: `boolean`):

Remember this search's results between scheduled runs and classify each job as NEW, UPDATED, UNCHANGED, REAPPEARED, or EXPIRED (changeType field). A job is UPDATED when any saved field differs from the last tracked run (title, description, deadline, workplace, salary, badges and so on; changed field names are listed in changedFields). Always excluded from the comparison: scrapedAt and expireDays, plus anything listed in Ignore fields when detecting changes. Unlike Resume from a previous run (which continues one specific run by pasted id), this actor tracks its own baseline automatically, keyed by the search filters below (or State key). Off by default — every run scrapes and saves everything it finds.

## `stateKey` (type: `string`):

Only used when Incremental & dedup mode is on. Leave empty to key the baseline automatically from the search filters (mode, keyword, state, cities, workplace/job types, companies, PWD/badge flags, URLs, Fetch details). Set your own text to track a named monitoring campaign explicitly, or to keep two schedules with identical filters from sharing one baseline.

## `emitUnchanged` (type: `boolean`):

Only used when Incremental & dedup mode is on. When off (default), a job identical to the last tracked run is skipped — not saved, not billed. When on, UNCHANGED rows are saved (and billed) too, alongside NEW/UPDATED/REAPPEARED ones.

## `emitExpired` (type: `boolean`):

Only used when Incremental & dedup mode is on. When on, a job tracked by a previous run but no longer found after a complete scan of this search is saved once with changeType EXPIRED (and billed as a normal result row). Only fires on a run that reached the natural end of the walk — Max items/Max pages capping the run, or Resume from a previous run being set, skips EXPIRED detection for that run and keeps the previous state as-is.

## `ignoreFieldsForChanges` (type: `array`):

Only used when Incremental & dedup mode is on. Field names (dotted paths for nested fields, for example source.salary) that never count as a change: they stay in every output row but cannot turn a job into UPDATED or appear in changedFields. scrapedAt and expireDays (a day count that drifts daily) are always excluded.

## `mcpConnectors` (type: `array`):

Optionally send the scraped jobs into the apps you already use, via Model Context Protocol (MCP) connectors. Authorize a connector once under Apify → Settings → Integrations, then select it here. The connector receives a condensed, human-readable summary per job (title + key fields), not the full JSON; the complete record stays in the dataset. Leave empty to skip. Supported: Notion (https://mcp.notion.com/mcp), Linear (https://mcp.linear.app/sse), Airtable (https://mcp.airtable.com/mcp), Apify (https://mcp.apify.com).

## `notionParentPageUrl` (type: `string`):

URL (or id) of the Notion page under which job pages are created. Required to enable the Notion export; ignored by other connectors.

## `maxNotifyListings` (type: `integer`):

Cap on jobs written to each connector per run. Does not affect the dataset.

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

Alias of Max items, accepted for compatibility. Max items wins when both are set.

## `feedback` (type: `string`):

Optional free-text note from an AI agent or user. Accepted and ignored.

## Actor input object example

```json
{
  "mode": "search",
  "searchTerm": "developer",
  "state": "",
  "cities": [],
  "workplaceTypes": [],
  "jobTypes": [],
  "companies": [],
  "pwdOnly": false,
  "friendlyBadgeOnly": false,
  "urls": [
    "https://portal.gupy.io/job-search/term=developer",
    "https://portal.gupy.io/job-search/term=developer&state=S%C3%A3o%20Paulo&workplaceTypes[]=remote",
    "https://portal.gupy.io/job-search/term=developer&sortBy=publishedDate&page=2",
    "https://tfcarreira.gupy.io/",
    "https://tfcarreira.gupy.io/job/eyJqb2JJZCI6MTI3NDg4MzIsInNvdXJjZSI6Imd1cHlfcG9ydGFsIn0=?jobBoardSource=gupy_portal",
    "https://portal.gupy.io/job-search/state=S%C3%A3o%20Paulo&city=S%C3%A3o%20Paulo",
    "https://portal.gupy.io/job-search/careerPageName[]=FARM"
  ],
  "fetchDetails": false,
  "sortBy": "newest",
  "maxItems": 20,
  "maxPages": 0,
  "pageSize": 100,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": []
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "ignoreFieldsForChanges": [],
  "maxNotifyListings": 50
}
```

# Actor output Schema

## `overview` (type: `string`):

No description

## `detail` (type: `string`):

No description

## `changes` (type: `string`):

No description

# 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 = {
    "searchTerm": "developer",
    "urls": [
        "https://portal.gupy.io/job-search/term=developer",
        "https://portal.gupy.io/job-search/term=developer&state=S%C3%A3o%20Paulo&workplaceTypes[]=remote",
        "https://portal.gupy.io/job-search/term=developer&sortBy=publishedDate&page=2",
        "https://tfcarreira.gupy.io/",
        "https://tfcarreira.gupy.io/job/eyJqb2JJZCI6MTI3NDg4MzIsInNvdXJjZSI6Imd1cHlfcG9ydGFsIn0=?jobBoardSource=gupy_portal",
        "https://portal.gupy.io/job-search/state=S%C3%A3o%20Paulo&city=S%C3%A3o%20Paulo",
        "https://portal.gupy.io/job-search/careerPageName[]=FARM"
    ],
    "sortBy": "newest",
    "maxItems": 20,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": []
    },
    "ignoreFieldsForChanges": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("abotapi/gupy-io-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 = {
    "searchTerm": "developer",
    "urls": [
        "https://portal.gupy.io/job-search/term=developer",
        "https://portal.gupy.io/job-search/term=developer&state=S%C3%A3o%20Paulo&workplaceTypes[]=remote",
        "https://portal.gupy.io/job-search/term=developer&sortBy=publishedDate&page=2",
        "https://tfcarreira.gupy.io/",
        "https://tfcarreira.gupy.io/job/eyJqb2JJZCI6MTI3NDg4MzIsInNvdXJjZSI6Imd1cHlfcG9ydGFsIn0=?jobBoardSource=gupy_portal",
        "https://portal.gupy.io/job-search/state=S%C3%A3o%20Paulo&city=S%C3%A3o%20Paulo",
        "https://portal.gupy.io/job-search/careerPageName[]=FARM",
    ],
    "sortBy": "newest",
    "maxItems": 20,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": [],
    },
    "ignoreFieldsForChanges": [],
}

# Run the Actor and wait for it to finish
run = client.actor("abotapi/gupy-io-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 '{
  "searchTerm": "developer",
  "urls": [
    "https://portal.gupy.io/job-search/term=developer",
    "https://portal.gupy.io/job-search/term=developer&state=S%C3%A3o%20Paulo&workplaceTypes[]=remote",
    "https://portal.gupy.io/job-search/term=developer&sortBy=publishedDate&page=2",
    "https://tfcarreira.gupy.io/",
    "https://tfcarreira.gupy.io/job/eyJqb2JJZCI6MTI3NDg4MzIsInNvdXJjZSI6Imd1cHlfcG9ydGFsIn0=?jobBoardSource=gupy_portal",
    "https://portal.gupy.io/job-search/state=S%C3%A3o%20Paulo&city=S%C3%A3o%20Paulo",
    "https://portal.gupy.io/job-search/careerPageName[]=FARM"
  ],
  "sortBy": "newest",
  "maxItems": 20,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": []
  },
  "ignoreFieldsForChanges": []
}' |
apify call abotapi/gupy-io-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,abotapi/gupy-io-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/G32z7UUaT9p3lAVY7/builds/ApM8Z5cJI96tIe201/openapi.json
