# Gupy Jobs Scraper (Vagas de Emprego) - Keyword, City & Company (`lanternlab318/gupy-jobs`) Actor

Search Gupy, Brazil's jobs platform, by keyword, city or company subdomain: title, company, city and state, workplace type, contract type, publish time, application deadline and job URL. No login or cookies. Deduplicated across searches; empty or cut-short searches are free and marked.

- **URL**: https://apify.com/lanternlab318/gupy-jobs.md
- **Developed by:** [Lantern Lab](https://apify.com/lanternlab318) (community)
- **Categories:** Jobs, Lead generation, Business
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$1.99 / 1,000 jobs

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

## Gupy Jobs Scraper (Vagas de Emprego)

A Lantern Lab Actor. Give it keywords, a Brazilian city, or Gupy company subdomains and get Gupy's public job listings (vagas de emprego) back: title, company, city and state, workplace type, contract type, publish time, application deadline, and the job URL. No Gupy login, no cookies, no account of your own.

Pay per job row: USD 1.99 per 1,000 jobs. Search-status rows, the run summary and empty results are free, and there is no run-start fee.

### What it does

- Searches each keyword separately and labels every row with the keyword that produced it, so one run can cover many keywords. The city filter, when given, applies to every search in the run.
- Collects a whole company's listings from its Gupy subdomain (the part before `.gupy.io`, for example `ambev` for ambev.gupy.io): the Actor resolves the subdomain to the company's exact name on Gupy and searches that board. Keywords and companies mix freely in one run.
- Deduplicates across the whole run: a job that matches several of your keywords, or that a company unit and a keyword unit both found, is written once and charged once.
- Keeps the source identifier (`jobId`), the platform's own times (`publishedAt`, `applicationDeadline`) and our collection time (`collectedAt`) as separate fields.
- Carries Gupy's own enums through untranslated: `workplaceType` is `hybrid`, `on-site` or `remote`, and `type` is a raw Gupy value such as `vacancy_type_effective` (effective/CLT role) or `vacancy_legal_entity` (legal-entity contract).
- Writes `country` as the constant `"Brazil"` on every row: Gupy is a Brazilian jobs platform and serves no other country, so this is a fact of the platform rather than a per-row field.
- Says what happened per search unit. A keyword Gupy answered with nothing is recorded as an empty result; a unit cut short by a limit, a refusal or an error is marked `partial: true` with the reason. The two are different records and are never confused.
- Caps itself: `maxUpstreamRequests` is a hard ceiling on requests to Gupy for the whole run, derived from your input when you leave it empty, so a run is never unbounded.
- Charges only job rows. Empty results, per-unit status rows and the run summary are free.

### What it does not do

- No job detail pages beyond the search listing: everything except the description comes from the search itself. The description is included in the listing and written only when you ask for it (`includeDescription`, off by default — descriptions are several kilobytes each).
- No salary data (Gupy does not publish it on listings) and no applicant or personal data of any kind.
- No other jobs platform, no translation, no login-only content.
- No scheduling or monitoring built in; use Apify schedules.

### Input

| Field | Default | Meaning |
| --- | --- | --- |
| `searchQueries` | required\* | One keyword per line, in Portuguese or any language Gupy matches: `analista de dados`, `dev jr`, `vendedor`. |
| `companies` | optional\* | Gupy company subdomains, the part before `.gupy.io` of a company's careers page: `ambev`, `afinz`. \*At least one keyword or one company is required. |
| `city` | empty | Filter every search to one city, as Gupy writes it: `São Paulo`, `Rio de Janeiro`. A misspelled city returns an empty result, not an error. |
| `maxJobs` | `100` | Upper bound on rows per keyword or per company, counting only fresh jobs. Capped at 1000. |
| `includeDescription` | `false` | Write the full description text on every row. Several kilobytes per job; the price per job does not change. |
| `maxUpstreamRequests` | derived | Hard ceiling on requests to Gupy for the whole run. Leave empty and the Actor works one out from your input. |
| `proxyConfiguration` | Apify Proxy | The proxy the requests go through. The datacenter default is what this Actor is measured on. |

```json
{
  "searchQueries": ["analista de dados", "cientista de dados"],
  "companies": ["ambev"],
  "city": "São Paulo",
  "maxJobs": 100
}
```

### Output

One row per job, `recordType: "job"`:

| Field | Meaning |
| --- | --- |
| `jobId` | Gupy's own numeric identifier for the listing; the dedupe key. |
| `title` | The job title as Gupy publishes it. |
| `company` | The company as Gupy names it on its career page. |
| `city`, `state` | The listing's location as Gupy writes it, or null when the listing names none. |
| `country` | The constant `"Brazil"` — Gupy operates in Brazil only. |
| `workplaceType` | Gupy's own enum: `hybrid`, `on-site`, `remote`. |
| `type` | Gupy's own contract-type enum, for example `vacancy_type_effective` or `vacancy_legal_entity`. |
| `publishedAt` | When Gupy published the listing, as an ISO 8601 instant. |
| `applicationDeadline` | Gupy's application deadline, or null while the listing is open-ended. |
| `description` | The full description text when `includeDescription` is on, null otherwise. |
| `url` | The listing's URL on the company's board. |
| `searchQuery` / `companyBoard` | Which unit produced this row: the keyword, or the company subdomain. Exactly one is set. |
| `rank` | Position in that unit's results, counting from 1. |
| `partial`, `partialReason` | Always `false` / `null` on a job row: the row exists because the job was collected. |
| `collectedAt` | Our own collection time, separate from `publishedAt`. |

One row per search unit, `recordType: "search_status"`, never charged:

| Field | Meaning |
| --- | --- |
| `jobsReturned` / `jobsCharged` | Rows this unit wrote and charged. |
| `duplicatesSkipped` | Jobs an earlier unit in the run had already collected. |
| `pagesFetched` | Job-search pages fetched for this unit. |
| `reportedAvailable` | Gupy's own `total` for the search, which Gupy itself caps at 100 — read it as "at least", not as an exact count. |
| `company` | For a company unit, the exact board name the subdomain resolved to. |
| `partial` / `partialReason` | `true` with `upstream_request_cap`, `charge_limit_reached`, `blocked`, `upstream_error` or `not_collected` when the unit's results are incomplete. |
| `statusText` | The same thing in a sentence, so the dataset explains itself. |

The last row is `recordType: "RUN_SUMMARY"` — jobs collected and charged, empty units, partial units by reason, company boards resolved, requests sent by endpoint, bytes moved, and why collection stopped. The same object is also in the run's key-value store under `RUN_SUMMARY`.

#### Partial is not empty

`jobsReturned: 0` with `partial: false` means Gupy has no job for that keyword or company. `partial: true` means the unit's results are incomplete and there may well be more. A run that collected nothing *and* had partial units fails rather than reporting an empty success.

### Limits and boundaries

- Only Gupy's public job search and public company boards. No login, no cookies, no account, no personal or applicant data.
- Gupy serves 12 jobs per page and reports `total` capped at 100, so `reportedAvailable` above 100 is a floor rather than a count; collection pages until your `maxJobs`, the end of the list, or the request ceiling.
- A company must be addressed by its exact Gupy subdomain. The name filter behind it is exact-match on Gupy's side; the Actor resolves the subdomain to the exact name for you, and a subdomain that is not a Gupy career board is reported on its status row rather than searched blindly.
- `city` is matched exactly as Gupy writes the city name.
- `publishedAt` and `applicationDeadline` are Gupy's own values and are not re-derived from the page.

# Actor input Schema

## `searchQueries` (type: `array`):

One search keyword per line, in Portuguese or any language Gupy matches, for example "analista de dados", "dev jr", "vendedor". Every keyword is searched separately and its results are labelled with it. A job that matches several of your keywords is returned once and charged once. At least one keyword or one company subdomain is required.

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

Optional. Gupy company subdomains to collect instead of, or besides, keywords: the part before .gupy.io of a company's careers page, for example "ambev" for ambev.gupy.io. The Actor resolves each subdomain to the company's exact name and searches its listings. A subdomain that is not a Gupy career board is reported on its status row.

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

Optional. Filter every search to one city, written as Gupy writes it, for example "São Paulo" or "Rio de Janeiro". Gupy matches the exact city name; a misspelled city returns an empty result, not an error.

## `maxJobs` (type: `integer`):

Upper bound on the rows one keyword or one company subdomain may produce, counting only fresh jobs. Collection for a unit stops at this limit, at the end of Gupy's list, or at the run's request ceiling - whichever comes first - and the unit's status row says which.

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

Write the full job description text on every row. Descriptions are several kilobytes each, so they are off by default and the description field is null; turn this on only when you need the text - the price per job does not change.

## `maxUpstreamRequests` (type: `integer`):

Hard ceiling on the HTTP requests this run sends to Gupy: job-search pages, company board pages and retries together. At the ceiling collection stops - the search units not reached are written with partialReason "upstream_request_cap" and the run then ends normally. Leave empty and the Actor works a ceiling out from your keywords, companies and maxJobs, so a run is never unbounded.

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

The proxy the requests to Gupy go through. The default (Apify Proxy, datacenter) is the cheapest and is what this Actor is measured on; Gupy serves its public job search over it without login.

## Actor input object example

```json
{
  "searchQueries": [
    "analista de dados"
  ],
  "maxJobs": 100,
  "includeDescription": false,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

No description

## `runSummary` (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 = {
    "searchQueries": [
        "analista de dados"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("lanternlab318/gupy-jobs").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 = { "searchQueries": ["analista de dados"] }

# Run the Actor and wait for it to finish
run = client.actor("lanternlab318/gupy-jobs").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 '{
  "searchQueries": [
    "analista de dados"
  ]
}' |
apify call lanternlab318/gupy-jobs --silent --output-dataset

```

## MCP server setup

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

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/AIkpqW2QOTrdpbA0S/builds/EZWP86XUE0gMe0j8O/openapi.json
