# LinkedIn LATAM Tech Jobs Scraper + Company Domain (`oswaldocarabano/linkedin-latam-tech-jobs-scraper`) Actor

Scrape LinkedIn tech jobs in Brazil, Mexico, Argentina, Colombia, Chile and 13 more Latin American countries, without login or cookies. Each job comes with the company website and domain, extracted tech stack, remote/hybrid, English level, salary, job poster and optional email pattern.

- **URL**: https://apify.com/oswaldocarabano/linkedin-latam-tech-jobs-scraper.md
- **Developed by:** [Oswaldo Carabano](https://apify.com/oswaldocarabano) (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 $2.00 / 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

## LinkedIn LATAM Tech Jobs Scraper + Company Domain

Scrape **LinkedIn tech jobs in Latin America** — Brazil, Mexico, Argentina, Colombia, Chile, Peru, Uruguay, Costa Rica and 10 more countries — **without a LinkedIn account or cookies**. Every job comes with the **hiring company's website and domain**, the full job description, the **tech stack** extracted from it, remote / hybrid / on-site, English level, salary when published, the **job poster** when LinkedIn shows one, and an optional **company email pattern** (first.last@…) with mail server check.

Built for developers in Latin America looking for their next job, recruiters and staffing agencies mapping who is hiring, and sales teams selling to companies that are growing their engineering teams. A LinkedIn jobs API alternative focused on LATAM tech jobs: Brazil, Mexico, Argentina, Colombia, Chile and Peru, plus remote jobs open to Latin America.

### What you get

**One row per job** (`outputType: jobs`, 117 fields across row types):

- **Job**: title, LinkedIn job ID and URL, location, city, region, country, posted date, applicants, seniority level, employment type, industries, apply type (on LinkedIn or on the company site), full description as text and HTML.
- **Derived from the posting**: tech stack (about 170 technologies: Python, React, AWS, Kubernetes, SQL, .NET…), role family (backend, frontend, data engineering, DevOps…), workplace type, English level, time zones, salary mentioned in the text, emails and links in the description, description language.
- **Company (always)**: website and **domain** — the key for outreach.
- **Company profile** (`includeCompanyDetails`, on by default): industry, size range, employees on LinkedIn, followers, headquarters with full address, type, founded year, specialties, description, office locations, similar companies with their industry, affiliated pages (subsidiaries and brands), and the company's latest posts (date, text, reactions, comments) — a quick signal of how active the company is.
- **Email pattern** (`inferEmailPattern`): most likely email format for the domain, confidence, evidence, MX check, email provider (Google Workspace, Microsoft 365…), and the job poster's likely email.

**Or one row per hiring company** (`outputType: companies`): the company profile with the open jobs this run found (titles, locations, countries, stack, latest posting date), the job posters, emails from its postings and the email pattern.

### Measured fill rates

Measured on 1 Oct 2026 on **639 jobs** from 8 countries (Brazil, Mexico, Argentina, Colombia, Chile, Peru, Uruguay, Costa Rica) × 4 role searches (software engineer, data engineer, frontend developer, devops). Company fields are measured on the 329 jobs whose company page was read in that sample.

| Field | Filled |
|---|---|
| Title, company, location, country, posted date, description | 100% |
| Seniority level, employment type | 100% |
| Industries | 99.7% |
| Tech stack (at least one technology) | 99.4% |
| Apply type (company site 73.7%, LinkedIn 25.0%) | 98.7% |
| Company website and domain | 97.6% |
| Company industry, size, type, employees on LinkedIn | 99.7% |
| Company headquarters | 97.0% |
| Company specialties | 87.5% |
| Company founded year | 70.8% |
| Company latest posts (of 120 company pages) | 95.0% |
| Company affiliated pages (of 120 company pages) | 48.3% |
| Applicant count as a number | 65.9% (the rest say "Be among the first 25", kept as an upper bound) |
| Workplace type (remote / hybrid / on-site) | 64.5% |
| English mentioned / English level | 47.6% / 29.6% |
| Job poster (name, headline, profile) | 10.2% |
| Emails in the description | 6.7% (all corporate; 0 personal-provider emails) |
| Time zones mentioned | 5.3% |
| Salary mentioned in the description | 3.0% |
| Salary range published on LinkedIn | 2.7% |

Rates differ by country (about 80 jobs each): job poster from 5.0% (Brazil) to 21.3% (Peru), emails in the description from 2.5% (Brazil, Colombia) to 13.8% (Costa Rica), workplace type from 55.0% (Brazil) to 71.3% (Peru, Costa Rica), LinkedIn salary range from 0% (Brazil, Argentina) to 7.5% (Chile). They are what LinkedIn publishes; the actor does not invent values — a field is `null` when the posting does not have it.

### How fast

Measured on Apify on 2 Oct 2026 (512 MB, 3 parallel requests):

| Input | Rows | Run time | Peak memory |
|---|---|---|---|
| Default input `{}` (100 jobs) | 100 jobs + 63 company profiles | 163 s | 124 MB |
| 2 keywords × 5 countries, 100 jobs | 100 jobs + 70 company profiles | 158 s | 140 MB |
| `{"maxItems": 40}` (6 runs, 2 of them at the same time) | 40 jobs + 26–27 company profiles | 47–73 s | 92–110 MB |
| Hiring companies, BR/MX/AR, email pattern, 20 (2 runs) | 19 companies | 26–30 s | 83–84 MB |
| React · Latin America · remote · email pattern, 30 | 30 jobs + 22 company profiles | 57 s | 87 MB |
| Data engineer · MX/CO · past week · mid-senior · full-time, 15 | 15 jobs (62 skipped by filters, not charged) | 68 s | 76 MB |

Across our 42 runs on Apify (4,039 requests to LinkedIn) no request was blocked (0 × 403/999); 60 answers (1.5%) were rate limits (429) and 13 were network errors, all retried on a new connection, with no page lost. One LinkedIn search reaches at most 1,000 jobs (LinkedIn's own limit); add keywords or countries to go further.

### Pricing

Pay per event — you pay for rows delivered, never for a page that failed or a job skipped by your filters:

| Event | Price |
|---|---|
| Actor start | $0.00001 |
| Job (with the full job page, website and domain) | $0.002 |
| Company profile (once per company per run) | $0.003 |
| Email pattern **with published evidence** (once per domain per run) | $0.01 |
| Email pattern inferred without evidence (`emailPatternInferred: true`) | free |
| No pattern found (`emailPattern: null`, e.g. a domain without mail servers) | free |

The email pattern is charged only when it is backed by emails the company published (`emailPatternInferred: false` and `emailPatternEvidenceCount` above 0). An inferred default pattern is still delivered, marked `emailPatternInferred: true`, and costs nothing; so does a domain where no pattern could be found (`emailPattern: null`).

Examples, measured on Apify, 2 Oct 2026 (rows and charged events read from each run, priced at the current prices):

| Run | Charged | Cost |
|---|---|---|
| Default input `{}` (100 jobs) | 100 jobs + 63 company profiles | 100 × $0.002 + 63 × $0.003 = **$0.39** |
| 2 keywords × 5 countries, 100 jobs | 100 jobs + 70 company profiles | 100 × $0.002 + 70 × $0.003 = **$0.41** |
| 40 jobs, `includeCompanyDetails: false` | 40 jobs | **$0.08** |
| 15 data engineer jobs, no company profiles | 15 jobs | **$0.03** |
| 30 remote React jobs with email patterns | 30 jobs + 22 profiles + 1 pattern with evidence (20 inferred, free) | 30 × $0.002 + 22 × $0.003 + 1 × $0.01 = **$0.14** |
| 19 hiring companies with email patterns | 19 companies (11 inferred patterns and 6 domains without mail servers, free) | 19 × $0.003 = **$0.06** |

Per 1,000 jobs: **$2.00** without company profiles. Company profiles add $0.003 per distinct company: 63–70 distinct companies per 100 jobs in our runs, so about **$3.90–4.10 per 1,000 jobs with company profiles** at that rate. Duplicates are never charged within a run, and a run never goes over your maximum cost per run (tested on Apify: a 200-job run with a $0.50 limit stopped cleanly below it).

### Input example

The input of one of our test runs on Apify (2 Oct 2026: 30 jobs in 57 s, $0.14 at the current prices):

```json
{
  "outputType": "jobs",
  "keywords": ["react"],
  "countries": ["LATAM"],
  "workplaceTypes": ["remote"],
  "inferEmailPattern": true,
  "maxItems": 30
}
```

Run it with an empty input `{}` and it returns 100 software engineer jobs from Brazil, Mexico, Argentina, Colombia and Chile posted in the last month, with company profiles.

### Output example

A real row from that run (shortened to 39 of its fields):

```json
{
  "jobId": "4443434628",
  "jobUrl": "https://www.linkedin.com/jobs/view/4443434628/",
  "title": "Staff Frontend Software Engineer",
  "companyName": "Tractian",
  "location": "São Paulo, São Paulo, Brazil",
  "country": "Brazil",
  "countryCode": "BR",
  "postedDate": "2026-09-26",
  "applicantsText": "Over 200 applicants",
  "applicants": 200,
  "seniority": "Not Applicable",
  "employmentType": "Full-time",
  "industries": [
    "Software Development"
  ],
  "roleCategory": "frontend",
  "workplaceType": "remote",
  "workplaceTypeSource": "description",
  "latamEligible": true,
  "latamEligibleReason": "located-in-latam",
  "englishMentioned": true,
  "englishLevel": "fluent",
  "stack": [
    "React"
  ],
  "descriptionLanguage": "en",
  "applyType": "external",
  "website": "http://www.tractian.com",
  "domain": "tractian.com",
  "companyIndustry": "Software Development",
  "companySize": "501-1,000 employees",
  "companyEmployeesOnLinkedin": 881,
  "companyFollowers": 176111,
  "companyHeadquarters": "Atlanta, Georgia",
  "companyType": "Privately Held",
  "companyFounded": 2019,
  "emailPattern": "first.last",
  "emailPatternConfidence": 0.1,
  "emailPatternInferred": true,
  "mxValid": true,
  "emailProvider": "Google Workspace",
  "catchAll": null,
  "scrapedAt": "2026-10-02T14:16:14.790Z"
}
```

### About the email pattern — read this

- The pattern is learned from emails and names that companies publish in their job postings. LinkedIn postings rarely contain emails (6.7% measured), so **most patterns are an inferred default with low confidence**: in a test run of 29 domains, 1 had published evidence and 28 were inferred at confidence 0.10. `emailPatternInferred: true` tells you which ones — and those are **not charged** (only patterns with published evidence are).
- What is always measured: whether the domain has mail servers (MX, DNS only) and which email provider it uses — 29 of 29 domains in that run.
- The actor **never sends an email and never probes a mailbox** (no SMTP). That is why `catchAll` is always null.

### Filters LinkedIn ignores, applied by the actor

For visitors without an account, LinkedIn only applies the keyword, the location and "posted within". We checked: the workplace type, experience level and job type filters return exactly the same jobs (13 of 13 variants). So this actor applies **workplace type, experience level and employment type itself**, from each job page. LinkedIn also fills searches with unrelated jobs when few match (a nonsense keyword still returns 10 jobs per page), so by default a job is kept only if it mentions your keyword (`requireKeywordMatch`) and is a tech role (`techOnly`). When 5 pages in a row bring nothing relevant, the search is treated as exhausted instead of reading all 1,000 unrelated jobs. Skipped jobs are never charged; the run summary counts them by reason.

### FAQ

**Do I need a LinkedIn account or cookies?** No. The actor reads the public job pages LinkedIn shows to visitors. It never logs in.

**Which countries?** Brazil, Mexico, Argentina, Colombia, Chile, Peru, Uruguay, Costa Rica, Ecuador, Guatemala, Panama, Dominican Republic, Bolivia, Paraguay, Venezuela, El Salvador, Honduras, Nicaragua, plus "Latin America" as a whole region. Each country uses the LinkedIn location ID we verified (8–10 of 10 jobs in the right country; the rest are metro areas like "Greater Curitiba").

**Are remote jobs for Latin America included?** Yes. Use `workplaceTypes: ["remote"]`. Workplace type is detected from the title, location and description (64.5% of jobs state it).

**Why is the salary empty so often?** Few Latin American postings publish one: 2.7% have a LinkedIn pay range and 3.0% mention a salary in the text. The text salary is only read next to a salary word, so insurance amounts or funding rounds are not mistaken for salaries.

**What about personal data?** Corporate emails published in postings are included. Emails at personal providers (Gmail, Outlook, Hotmail…) are withheld unless you turn on `includePersonalEmails` — in the email lists and also inside the description and company texts, where they are replaced by `[email withheld]`. Job posters are included as LinkedIn shows them publicly, except for jobs located in the EU, EEA, UK or Switzerland, where they are always withheld. Names and photos of a company's employees are never read.

**What happens if LinkedIn limits requests?** The actor paces itself at about one request per second per connection (the pace we measured to stay clear of rate limits), switches connection on a refusal and pauses when refusals pile up. Pages that still cannot be read are listed in the `ERRORS` record and never charged.

**What happens when my maximum cost per run is reached?** The run stops cleanly. No row is delivered without being charged.

**How much does it cost to scrape 1,000 LinkedIn jobs?** $2.00 for the jobs ($0.002 each, with full description, stack, website and domain). Company profiles are optional and add $0.003 per distinct company: with 63–70 distinct companies per 100 jobs (measured on Apify, 2 Oct 2026), about $3.90–4.10 per 1,000 jobs with profiles.

**Does it work from Apify's servers?** Yes. All the figures on this page were measured on Apify, including two runs started at the same time (each run uses its own connections).

**Can I get more than 1,000 jobs?** One LinkedIn search stops at 1,000. Add more keywords or countries: each keyword × country is its own search, and the budget is shared between them in turns.

### Run outputs

- **Dataset**: one row per job (or per company). Views: Jobs, Outreach, Salary, Hiring companies, Errors.
- **RUN_SUMMARY**: rows delivered and charged, jobs skipped by reason, network summary.
- **ERRORS**: pages that could not be read (always written, empty when all went well).

# Actor input Schema

## `outputType` (type: `string`):

One row type per run. "Jobs" gives one row per job posting (the main product). "Hiring companies" gives one row per company that is hiring for your search, with its open jobs, website, domain and profile.

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

Job titles or skills to search for, like the LinkedIn search box (e.g. "software engineer", "react developer", "data engineer", "devops"). Each keyword is a separate search in every country. Plain words only: no URLs. Up to 10.

## `countries` (type: `array`):

Latin American countries to search in. "Latin America (whole region)" searches the LinkedIn region as one place. The budget (Max items) is shared in turns between every keyword and country.

## `postedWithin` (type: `string`):

Only jobs posted in this period. Applied by LinkedIn itself.

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

Keep only remote, hybrid or on-site jobs. LinkedIn ignores this filter for visitors without an account, so the actor reads it from the job title, location and description (detected in 64.5% of measured jobs). Jobs whose posting does not say it are skipped when this filter is set. Skipped jobs are never charged. Empty = all.

## `experienceLevels` (type: `array`):

Keep only these LinkedIn seniority levels (read from each job page, 100% filled). Skipped jobs are never charged. Empty = all.

## `employmentTypes` (type: `array`):

Keep only these employment types (read from each job page, 100% filled). Skipped jobs are never charged. Empty = all.

## `techOnly` (type: `boolean`):

Skip postings that are not software, data, cloud, QA, security, product or IT roles (decided from the title, or from 3+ technologies in the description). Skipped jobs are never charged.

## `requireKeywordMatch` (type: `boolean`):

LinkedIn fills searches with unrelated jobs when few match (a nonsense keyword still returns 10 jobs per page). With this on, a job is kept only if its title or description contains every word of the keyword. Skipped jobs are never charged.

## `includeCompanyDetails` (type: `boolean`):

Add the hiring company's public LinkedIn profile to each row: industry, size, employees on LinkedIn, followers, headquarters and full address, type, founded year, specialties, description, office locations and similar companies. Charged once per company per run. The company website and domain are always included, even with this off.

## `inferEmailPattern` (type: `boolean`):

Infer the company's email format (e.g. first.last@example.com) from the emails and names published in its job postings, check the domain's mail servers (MX, DNS only) and detect the email provider. When the job poster is known, adds their likely email. Never sends an email or probes a mailbox. Charged once per domain per run, only when the pattern is backed by published emails; an inferred pattern is delivered free and marked emailPatternInferred: true.

## `includePersonalEmails` (type: `boolean`):

Emails at personal providers (Gmail, Outlook, Hotmail, Yahoo, iCloud, Proton…) found in job descriptions are withheld by default and only counted. Turn on to include them.

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

Maximum rows to deliver (jobs, or companies). Each LinkedIn search reaches at most 1,000 jobs; add keywords or countries to go further.

## `maxConcurrency` (type: `integer`):

Job pages read at the same time. Each one uses its own connection at about one request per second, the pace measured to stay clear of LinkedIn rate limits. With 3, 100 jobs with company profiles took 158–163 s in our test runs on Apify.

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

Leave empty: the actor uses its own residential connection. Set it only to route through your own Apify Proxy group or proxy URLs.

## Actor input object example

```json
{
  "outputType": "jobs",
  "keywords": [
    "software engineer",
    "data engineer"
  ],
  "countries": [
    "BR",
    "MX",
    "AR",
    "CO",
    "CL"
  ],
  "postedWithin": "month",
  "workplaceTypes": [],
  "experienceLevels": [],
  "employmentTypes": [],
  "techOnly": true,
  "requireKeywordMatch": true,
  "includeCompanyDetails": true,
  "inferEmailPattern": false,
  "includePersonalEmails": false,
  "maxItems": 100,
  "maxConcurrency": 3,
  "proxyConfiguration": {}
}
```

# Actor output Schema

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

One row per job: title, company, location, seniority, employment type, applicants, description, tech stack, workplace type, salary, company website and domain, and optional company profile and email pattern.

## `runSummary` (type: `string`):

Rows delivered and charged, jobs skipped by your filters, network summary and whether the run stopped early.

## `errors` (type: `string`):

Job or company pages that could not be read (always written, empty when all went well). Never charged.

# 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": [
        "software engineer",
        "data engineer"
    ],
    "countries": [
        "BR",
        "MX",
        "AR",
        "CO",
        "CL"
    ],
    "maxItems": 100
};

// Run the Actor and wait for it to finish
const run = await client.actor("oswaldocarabano/linkedin-latam-tech-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 = {
    "keywords": [
        "software engineer",
        "data engineer",
    ],
    "countries": [
        "BR",
        "MX",
        "AR",
        "CO",
        "CL",
    ],
    "maxItems": 100,
}

# Run the Actor and wait for it to finish
run = client.actor("oswaldocarabano/linkedin-latam-tech-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 '{
  "keywords": [
    "software engineer",
    "data engineer"
  ],
  "countries": [
    "BR",
    "MX",
    "AR",
    "CO",
    "CL"
  ],
  "maxItems": 100
}' |
apify call oswaldocarabano/linkedin-latam-tech-jobs-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,oswaldocarabano/linkedin-latam-tech-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/vF6hot4WYeYe23qgb/builds/jmpROsAgYnIFv3OoJ/openapi.json
