# OLX Brazil Scraper API (`dami_studio/olx-brazil-scraper`) Actor

Read olx.com.br search and category pages and get one row per listing: id, title, price in BRL, category, city and state, publish date, image URLs, per-category attributes and whether the seller is private or a business. $0.85 per 1,000 listings.

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

## Pricing

Pay per event

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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## OLX Brazil Scraper API

Reads search and category result pages on **olx.com.br** and gives you one row per listing: id,
title, price in BRL, category, city and state, publish date, image URLs, whether the seller is
private or a business, and the listing URL.

It runs without a browser and never fetches a per-listing detail page, because it doesn't need to.
About 50 listings come back per request. Seller phone numbers are not included, and the section
below explains why.

### What it returns

One row per listing. The percentages are real coverage measured on the shipped build over a
300-row run on `?q=iphone` and a 120-row run on `imoveis/venda/estado-rj`, both on 2026-08-10:

| Field | Filled | Example |
| --- | --- | --- |
| `listingId` | 100% | `1517568906` |
| `title` | 100% | `iPhone 12 Pro Max 128GB \| Conservado \| Caixa Original` |
| `price` / `priceLabel` | 100% | `3000` / `R$ 3.000` |
| `currency` | 100% | `BRL` |
| `oldPrice` / `oldPriceLabel` | 19% | `3200` / `R$ 3.200`, only when the seller cut the price |
| `category` / `categoryId` / `parentCategoryId` | 100% | `Celulares e Smartphones` / `3060` / `3000` |
| `city` / `state` / `stateName` | 100% | `São Paulo` / `SP` / `São Paulo` |
| `neighbourhood` | 95–98% | `Paraíso do Morumbi` |
| `areaCode` / `locationLabel` | 100% | `11` / `São Paulo, Paraíso do Morumbi - DDD 11` |
| `publishedAt` / `publishedTimestamp` | 100% | `2026-08-10T01:54:40.000Z` / `1786413280` |
| `sellerType` | 100% | `private` or `business` |
| `sellerName` | 100% | `Fatima` — the public display name on the ad |
| `thumbnailUrl` / `imageUrls` / `imageCount` | 100% | first image, all images, count |
| `hasVideo` | always present | `true` on 0% of both runs |
| `isFeatured` | always present | `true` on 25% (phones), 1% (property) |
| `isBumped` | always present | `true` on 1% (phones), 0% (property) |
| `hasFreeShipping` | always present | `true` on 100% (phones), 0% (property) |
| `attributes` | 100% | `{ "Marca": "APPLE", "Modelo": "IPHONE 12 PRO MAX", "Condição": "Usado" }` |
| `url` | 100% | the listing page |
| `searchUrl`, `page`, `position`, `scrapedAt` | 100% | provenance |
| `searchQuery` | only when you passed a search term | `iphone` |

Attribute keys come straight from OLX and differ per category. On the phone run every row carried
`Categoria`, `Condição`, `Cor`, `Marca`, `Modelo`, plus `Memória interna` and `Saúde da bateria` on
98%. On the property run every row carried `Categoria`, `Tipo`, `Quartos`, `Banheiros`, plus
`Condomínio` on 99% and `IPTU` on 95%.

### What it does not do

- **No seller phone numbers.** They are not in the payload this actor reads, and it does not go
  looking for them anywhere else. `sellerType` and the public display name are all you get.
- **No single-listing pages.** Paste a search or category URL, not an ad URL. An ad URL returns an
  uncharged diagnostic row explaining that.
- **No jobs.** `vagas-de-emprego` is a separate OLX product that renders through a different
  component and carries no listing payload on the results page.
- **No more than about 5,000 listings per search.** OLX serves 50 per page and repeats page 100
  forever after that — `?o=101` and `?o=150` return byte-for-byte the same 50 ads as `?o=100`.
  Narrow by state, category or price band to reach deeper stock.
- **No "highest price first" sort.** OLX offers it in its own UI but does not expose it as a URL
  parameter that survives a plain request. Relevance, newest and lowest-price all work.

### Input

```json
{
  "searchQueries": ["notebook"],
  "category": "informatica",
  "state": "SP",
  "sortBy": "date",
  "minPrice": 500,
  "maxPrice": 3000,
  "maxItems": 250
}
```

Or paste URLs and keep every filter you already set in the OLX UI:

```json
{
  "startUrls": ["https://www.olx.com.br/celulares/estado-sp?q=iphone&ps=1000&pe=3000"],
  "maxItems": 100
}
```

Every input above was checked against live result counts before it was exposed: `state` cut an
`iphone` search from 103,531 to 19,372 listings, the `ps`/`pe` price band cut it to 46,186,
`category` + `state` and both sort orders all changed the returned set.

`region` narrows inside a state using the slug OLX shows in its own URLs, for example
`sao-paulo-e-regiao`. **A region slug OLX does not recognise is ignored silently.** You get the
whole state back with no error at all. This actor detects that and adds one uncharged `REGION_NOT_APPLIED` row
listing the region slugs that would have worked.

Empty input returns one labelled sample row and charges nothing.

### How it works, and why it used to fail

OLX is a Next.js App Router site with no public JSON search endpoint. The listing data is in the
React Server Component payload, as a plain `"ads": [ ... ]` array with every listing already
denormalised — no detail-page fetch is needed for any field listed above.

The hard part was reaching it. olx.com.br is behind a Cloudflare firewall rule that refuses most
datacenter addresses with a 5.8 KB "Sorry, you have been blocked" page, which is why earlier
versions of this actor succeeded or failed apparently at random.

That rule only matches requests shaped like a top-level page navigation. Measured across 14 fresh
Apify datacenter exit addresses, same minute, same TLS fingerprint:

| Request shape | Addresses served |
| --- | --- |
| plain page request | 1 / 14 |
| `+ x-requested-with: XMLHttpRequest` | 8 / 14 |
| `+ RSC: 1` and a same-origin `referer` | 8 / 14 |

The actor sends both headers and takes the RSC form, whose response is the flight payload alone.
That's 260 KB instead of 730 KB, and about twice as fast. For the addresses still refused, it walks a
ladder of exit addresses until one is served and then pins it for the rest of the run; a pinned
address returned 25 pages out of 25 without a single refusal. Requests use `impit` with Chrome's
real TLS fingerprint, because plain Node is rejected at the handshake.

Measured on the Apify platform, 2026-08-10: **32 of 32 runs succeeded**, including 10 deliberately
run with the container's own address disabled, so that every request had to go through the exit
ladder. Those 10 needed at most 3 addresses each. A 999-listing run took 58 seconds.

### Billing

**$0.85 per 1,000 listings**, plus a $0.001 start fee per run. It's a flat rate, the same number on
the free plan as on any paid one.

Blocked requests, empty results, bad URLs, the region warning and the sample row are never charged.
You pay for listings actually returned, one charge per row.

### FAQ

**Can I scrape OLX Brazil without a Brazilian IP address?**
Yes. This actor runs from ordinary US datacenters and never uses a Brazilian address. OLX's
firewall rule is about address reputation and request shape, not geography.

**Why did my run return zero rows and a `BLOCKED` row?**
Every exit address the run had available was refused. That's rare now, 0 of 32 test runs hit it,
but if it happens, re-run, or put your own proxy URLs in `proxyConfiguration`. Nothing was charged.

**How many listings can I get for one search term?**
About 5,000: 50 per page, 100 pages, then OLX repeats page 100. Split by state, category or price
band to go deeper. A 1,000-listing run took 58 seconds.

**Does it return the seller's phone number?**
No, by design.

**Can I monitor new listings?**
Set `sortBy` to `date` and a small `maxItems`. Newest ads come first and every row carries
`publishedAt`, so you can diff against your last run.

**What currency are prices in?**
Brazilian reais. `price` is numeric, `priceLabel` keeps OLX's own formatting.

**Does it work for property and cars, not just goods?**
Yes. `imoveis/venda`, `imoveis/aluguel` and `autos-e-pecas/carros-vans-e-utilitarios` are all
result pages with the same payload; a property run returns `Quartos`, `Banheiros`, `Condomínio` and
`IPTU` in `attributes`.

# Actor input Schema

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

What to look for on olx.com.br, one term per line. OLX is a Brazilian site, so Portuguese terms return the most ("iphone", "notebook", "bicicleta aro 29", "apartamento 2 quartos").

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

Optional. Limit the search to one Brazilian state. Accepts the two-letter code ("SP", "RJ", "MG") or the full name ("Sao Paulo"). Leave empty to search all of Brazil.

## `region` (type: `string`):

Optional. Narrows the search inside the state, using the region slug OLX shows in its own URLs, for example "sao-paulo-e-regiao", "rio-de-janeiro-e-regiao". Requires a state; ignored without one. OLX silently returns the whole state for a slug it does not recognise, so this Actor checks and adds one uncharged warning row listing the slugs that would have worked. Leave empty for the whole state.

## `category` (type: `string`):

Optional OLX category path. Pick one here, or type any path you see in an OLX URL (for example "imoveis/venda" or "autos-e-pecas/carros-vans-e-utilitarios").

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

Hard cap on rows returned across all searches (1-5000). You are charged per listing returned. OLX serves about 50 listings per page and stops paginating after page 100, so one search tops out near 5000 listings.

## `startUrls` (type: `array`):

Optional. Paste OLX search or category result pages and every filter already on the URL is kept, for example https://www.olx.com.br/celulares/estado-sp?q=iphone\&ps=1000\&pe=3000 . Works alongside the fields above. Single listing pages are not supported.

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

How OLX orders the results. "Newest first" is the one to use for monitoring new listings.

## `minPrice` (type: `integer`):

Optional. Only return listings priced at or above this, in Brazilian reais.

## `maxPrice` (type: `integer`):

Optional. Only return listings priced at or below this, in Brazilian reais.

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

Optional. The actor already reaches OLX on its own; leave this alone unless you need to exit from a specific network. Your own proxy URLs are used exactly as given. Metered Apify groups (RESIDENTIAL, GOOGLE\_SERP) are not offered and are replaced with datacenter addresses.

## Actor input object example

```json
{
  "searchQueries": [
    "iphone"
  ],
  "state": "",
  "category": "",
  "maxItems": 50,
  "startUrls": [],
  "sortBy": "relevance",
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

One dataset row per OLX classified listing found. Empty input and failed searches produce an uncharged sample or diagnostic row instead.

# 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": [
        "iphone"
    ],
    "maxItems": 50,
    "startUrls": [],
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("dami_studio/olx-brazil-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 = {
    "searchQueries": ["iphone"],
    "maxItems": 50,
    "startUrls": [],
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("dami_studio/olx-brazil-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 '{
  "searchQueries": [
    "iphone"
  ],
  "maxItems": 50,
  "startUrls": [],
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call dami_studio/olx-brazil-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,dami_studio/olx-brazil-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/IL5oTKcO8WTHTN7qT/builds/joNczhDahhT7wPUac/openapi.json
