# Crexi Commercial Real Estate Listings & Broker Scraper (`scrapebench/crexi-commercial-real-estate-listings-scraper`) Actor

Commercial property listings from Crexi with address, county, asking price, coordinates and the listing broker — and paywall placeholders dropped instead of passed through.

- **URL**: https://apify.com/scrapebench/crexi-commercial-real-estate-listings-scraper.md
- **Developed by:** [ScrapeBench](https://apify.com/scrapebench) (community)
- **Categories:** Real estate, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$0.003 / listing

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

## Crexi Commercial Real Estate Listings & Broker Scraper

### Pain points

- Crexi writes paywalled values as runs of asterisks rather than leaving them empty. Over 12 retail listings checked on 2026-09-07, 'Year Built' came back as '\*\*\*\*' on 9 of the 9 that had the field, 'APN' on 6 of 6, and Cap Rate, Parking, Tenancy, Units and Price per SqFt on every occurrence. Copied straight through, those land in your spreadsheet as text that passes an is-not-null filter.
- The API refuses to page past 1,499 results. The catalogue held 242,488 listings on 2026-09-07 and a single city can exceed the window on its own — Austin alone matched 2,367 — so a sweep that looks complete is often the first 1,499 of many, with nothing saying so.
- Two filters look supported and are not. Sending isLease, or an askingPriceFrom/askingPriceTo band, returns exactly the same 242,488 matches as sending neither.
- A commercial-listing scraper that quietly returns nothing still finishes as a SUCCEEDED run, so the Store's failure rate cannot show it. 'The site changed and we now get zero rows' and 'there is nothing here' are written identically in a dataset.
- Broker email addresses and phone numbers are not published on Crexi's API at any tier, which is worth knowing before you buy a scraper on the promise of them.

### What we solve

- Paywalled placeholders are dropped, so a field is either a real published value or absent — never '\*\*\*\*'.
- When a search matches more than Crexi will page through, the run returns a free row stating how many it matched against how many it could reach.
- Price filtering is done here, on the values actually returned, because Crexi's own price parameters do nothing.
- An outage or a changed response shape returns a free, unbilled marker row saying which — never zero rows on a successful-looking run.
- A property type Crexi does not recognise returns a free row naming the eleven it does, instead of failing the run.
- Many locations in one run, with the result cap applied per location.

### Summary

Searches Crexi by city, county, state or free text and returns one row per commercial listing: name, full address with county, city, state, ZIP, Crexi's own latitude and longitude, asking price, square footage, derived price per square foot, property types, status, opportunity-zone flag, whether an offering memorandum is attached, image count, listed and updated dates, and a link to the listing page. Turn on detail enrichment for the broker's full write-up and Crexi's detail table, and broker enrichment for the listing broker's name, brokerage, public profile and how many listings they currently hold. Submit many locations in one run; the result cap applies per location, so one busy city cannot crowd out the rest.

### Who it's for

- Commercial brokers and investors building deal pipelines by market
- Lenders and appraisers pulling comparable listings by county and type
- Proptech and CRE data teams ingesting listing inventory on a schedule
- Market researchers measuring inventory, pricing and days-on-market by metro

### How to use

Set the input, run the actor, and collect results from the run's dataset (export to JSON/CSV/Excel, or pull via the Apify API). Example input:

```json
{
  "queries": [
    "austin texas",
    "san antonio texas"
  ],
  "types": [
    "Retail",
    "Office"
  ],
  "maxResults": 200,
  "includeBrokers": true
}
```

See **Inputs** below for every available field.

### What you get

One row per record:

| Field | Description |
|---|---|
| `listing_id` | Crexi's own asset id — stable, so it de-duplicates across runs |
| `name / description` | The listing title and the short marketing headline |
| `property_types` | Crexi's types for the listing; a property can carry several |
| `status` | On-Market, Under Contract, and so on |
| `asking_price` | USD. Null where the listing is marketed without a published price |
| `square_footage` | Building size, where published |
| `price_per_sqft` | Derived from asking price and square footage, only when both are real numbers |
| `address / full_address` | Street address, and Crexi's formatted street, city, county, state and ZIP |
| `city / county / state / state_name / zip` | Parsed location components |
| `latitude / longitude` | Crexi's own per-listing coordinate — not a geocode we derived |
| `brokerage_name` | The firm marketing the listing, on every row without enrichment |
| `broker_name / broker_profile_url / broker_listing_count` | The listing broker, their public Crexi profile and how many listings they hold. Only with broker enrichment on |
| `in_opportunity_zone / has_offering_memorandum / has_flyer` | Listing flags Crexi publishes |
| `image_count / thumbnail_url` | How many photos the listing has, and Crexi's hosted thumbnail |
| `activated_on / updated_on` | When the listing went live and when it last changed — this is what makes a new-listings run possible |
| `marketing_description` | The broker's full write-up. Only with detail enrichment on |
| `details` | Crexi's own detail table — zoning, lot size, year built, cap rate, APN — carrying only the entries it actually publishes |
| `market_url` | The listing page on crexi.com |
| `source_url` | The Crexi endpoint this row came from |

Sample:

```json
{
  "source_url": "https://api.crexi.com/assets/2661656",
  "listing_id": "2661656",
  "name": "6229 Old Lockhart Road",
  "description": null,
  "property_types": [
    "Retail",
    "Industrial",
    "Land"
  ],
  "status": "On-Market",
  "asking_price": 650000.0,
  "square_footage": null,
  "price_per_sqft": null,
  "address": "6229 Old Lockhart Road",
  "full_address": "6229 Old Lockhart Road, Buda, Travis County, TX 78610",
  "city": "Buda",
  "county": "Travis County",
  "state": "TX",
  "state_name": "Texas",
  "zip": "78610",
  "latitude": 30.0733165,
  "longitude": -97.7213696,
  "brokerage_name": "John B. Sanford Real Estate",
  "broker_name": "J.D. Sanford",
  "broker_profile_url": "https://www.crexi.com/brokers/jdsanf",
  "broker_listing_count": 21,
  "in_opportunity_zone": false,
  "has_offering_memorandum": false,
  "has_flyer": true,
  "image_count": 4,
  "activated_on": "2026-08-13T18:35:24.429Z",
  "updated_on": "2026-08-19T14:40:14.293Z",
  "details": {
    "Asking Price": "$650,000",
    "Property Type": "Retail, Industrial, Land",
    "Zoning": "Residential",
    "Lot Size (acres)": "8.32",
    "Ground Lease": "No"
  },
  "market_url": "https://www.crexi.com/properties/2661656/texas-6229-old-lockhart-road"
}
```

### Inputs

| Field | Required | Type | Default | Description |
|---|---|---|---|---|
| `query` | no | string | `"austin texas"` | Where to search — a city, county, state or free-text phrase: 'austin texas', 'harris county', 'miami'. Leave blank to sweep the whole catalogue (242,488 listings on 2026-09-07), though Crexi will only page 1,499 deep into any one search, so a location is usually what you want. Kept forever alongside 'Locations' — saved tasks and API callers send it. |
| `queries` | no | array | `["austin texas", "san antonio texas"]` | Search many locations in ONE run instead of starting a run per city. Takes the same values as 'Location or keyword'. Combined with it if you fill both, and duplicates are removed. The result cap below applies PER location, so one city cannot eat the whole run and leave the others looking empty. |
| `types` | no | array | `["Retail", "Office"]` | Crexi's own property types, exactly as it spells them: Land, Retail, Office, Industrial, Multifamily, MixedUse, SpecialPurpose, Hospitality, SelfStorage, MobileHomePark, SeniorLiving. Anything else is rejected by Crexi, so an unrecognised value returns a free row naming the valid ones rather than failing the run. Leave empty for all types. |
| `minPrice` | no | integer | — | Applied after fetching, because Crexi accepts askingPriceFrom and then ignores it — the total comes back unchanged either way. Setting any band drops listings with no published price ('Call for offers'), since a band is a statement about price that an unpriced listing cannot satisfy. |
| `maxPrice` | no | integer | — | See 'Minimum asking price'. Leave both empty to keep unpriced listings. |
| `includeDetails` | no | boolean | `false` | Adds the marketing description and Crexi's detail table (zoning, lot size, year built, cap rate, APN and so on) with one extra request per listing. Only the entries Crexi actually publishes come through: it writes paywalled values as runs of asterisks ('Year Built': '\*\*\*\*'), and those are dropped rather than handed to you as text that survives an is-not-null filter. |
| `includeBrokers` | no | boolean | `false` | Adds the listing broker's name, brokerage, public profile link and how many listings they hold, with one extra request per listing. Crexi publishes no broker email or phone number on this API at any tier, so those are not returned and are not implied. |
| `maxResults` | no | integer | `200` | Cap on listings returned PER location, not per run — so a batch cannot silently return only the first rows overall and read as 'these cities have nothing'. Crexi refuses to page beyond 1,499 into any single search, so that is the hard ceiling per location whatever you set; when a search matches more, the run says how many it matched. |
| `proxyConfiguration` | no | object | `{"useApifyProxy": false}` | Optional and off by default. Crexi's search API answered a plain client in about 0.24s from an ordinary connection, so a proxy is not needed and only adds latency (#145: never reach for one before the plain path is proven to fail). |

### Pricing (Pay Per Event)

You pay per result (`listing`) — **no charge for empty runs**. Example: **1000 listings** at *$0.003/result* ≈ **$3.00**.

The default input — Retail and Office across Austin and San Antonio, capped at 200 per city — returns 400 listings, so a default run costs $1.20 and takes under two seconds. You are charged per listing returned, not per location submitted: 1,000 listings cost the same from one city or from ten. Detail and broker enrichment add requests, not charges. Apify platform usage (compute) is billed separately per your plan.

### Use cases

- Market inventory — sweep every retail and office listing in a metro with asking price, square footage and coordinates, ready to map.
- New-listing alerts — run on a schedule and filter on the listed date to see what came to market since the last run.
- Broker mapping — turn on broker enrichment to see who is marketing inventory in a county and how many listings each of them holds.
- Price-band screening — pull only listings inside a budget, with unpriced 'call for offers' listings excluded automatically.

### Why this actor

- Crexi's paywall placeholders are stripped to empty rather than delivered as '\*\*\*\*' — measured masked on every occurrence of Year Built, APN, Cap Rate, Tenancy, Parking, Units and Price per SqFt in a 12-listing sample on 2026-09-07.
- The 1,499-result paging ceiling is reported against Crexi's own match count, so a partial sweep can never read as a complete one.
- County, latitude and longitude come from Crexi's own listing record on every row, with no geocoding step and no derived approximation.
- Every failure mode returns a free, unbilled marker row explaining itself, so a zero-row run always says why.
- Batch many locations per run, with the cap applied per location rather than per run.
- $3 per 1,000 listings, against $0.99 to $30 across this category on the Store (2026-09-07).

### Limitations & updates

Covers listings published on Crexi's public search API. The hard ceiling is Crexi's: no single search can page beyond 1,499 results, out of a catalogue of 242,488 on 2026-09-07, so wide sweeps must be split by location, property type or price band — the run says when it hit the ceiling. No broker email or phone number is available from this API at any tier. Lease-versus-sale filtering is not offered because Crexi's own parameter for it has no effect. Attributes Crexi puts behind its paywall (frequently Year Built, APN, Cap Rate, Parking, Tenancy, Units, Price per SqFt) are returned empty rather than as its asterisk placeholders — they are not available to any unauthenticated reader. Detail and broker enrichment each cost one extra request per listing, so a large run with both on takes noticeably longer. Data is whatever Crexi shows at the moment of the run; nothing here is estimated, geocoded or inferred by a model, and this Actor returns no owner or personal information.

### FAQ

**What are the asterisks in Crexi's data, and what do you do with them?**

Crexi puts some listing attributes behind its own paywall, and instead of leaving them empty it returns a string of asterisks: 'Year Built': '****', 'APN': '\*\*\*\*\*\*\*\*', 'Cap Rate': '****\***'. Over 12 retail listings sampled on 2026-09-07 that happened on 9 of the 9 listings that carried Year Built, 6 of 6 for APN, and every occurrence of Cap Rate, Parking, Tenancy, Units, Beds, Net Rentable and Price per SqFt. This Actor drops them, so a field is either a real published value or absent. It matters more than it sounds: '**\*\*' is a non-empty string, so it passes an is-not-null filter, sorts as text, and quietly ruins any count of how complete your data is.

**How many listings can one search return?**

Up to 1,499 per location, and that ceiling is Crexi's rather than ours — the API rejects any request where offset plus count reaches 1500. The catalogue held 242,488 listings on 2026-09-07, and a single city can exceed the window: Austin matched 2,367 with no type filter. When a search matches more than can be paged, the run returns a free row saying how many matched against how many were reachable, so you know to narrow by property type, city or price band rather than assuming you have everything.

**Can I filter by price, or by lease versus sale?**

Price yes, lease-versus-sale no. Crexi's API accepts askingPriceFrom and askingPriceTo and then ignores them — with or without a band the match count is identical — so price is filtered here, on the values actually returned. Its isLease parameter behaves the same way, and rather than offer a switch that silently does nothing, this Actor does not have one. Setting any price band excludes listings marketed without a published price, since 'call for offers' cannot satisfy a band.

**Do I get broker email addresses and phone numbers?**

No, and no Crexi scraper can give you them from this API — the broker endpoint publishes a first and last name, the brokerage, a headshot, a public profile id and a listing count, and no contact fields at any tier. What you get is the broker's name, their brokerage, a link to their public Crexi profile and how many listings they currently hold, which is enough to identify and rank who is active in a market. Anything advertising CRE broker emails is getting them from somewhere other than here.

**Which property types can I search?**

Crexi's own eleven, spelled its way: Land, Retail, Office, Industrial, Multifamily, MixedUse, SpecialPurpose, Hospitality, SelfStorage, MobileHomePark and SeniorLiving. Their live counts on 2026-09-07 were Land 118,855, Retail 57,038, Office 38,854, Industrial 29,049, Multifamily 28,964, MixedUse 22,304, SpecialPurpose 15,999, Hospitality 5,711, SelfStorage 2,367, MobileHomePark 1,097 and SeniorLiving 838. A value Crexi does not recognise is rejected by Crexi with an error, so this Actor checks first and returns a free row naming the valid ones rather than failing the run.

**What happens if Crexi is down or changes its response?**

You get one free, unbilled row saying so — notice\_type 'blocked' for an outage or refusal, 'source\_changed' if the response no longer carries the result envelope this Actor reads. That distinction is the point: a commercial-listing scraper that starts returning nothing still completes as a SUCCEEDED run, so nothing on the Store's own failure rate can reveal it, and 'the source moved' and 'there are no listings here' look identical in a dataset unless one of them says which it is.

**Do the coordinates come from Crexi or from a geocoder?**

From Crexi. Latitude, longitude and county are on its own listing record, so every row carries a point the source itself published rather than one derived from an address string. That matters for mapping: a geocoded address can land on a street centroid or a city centroid and still look plausible.

**How do I get only listings that are new since my last run?**

Every row carries activated\_on, the date Crexi made the listing live, and updated\_on for its last change. Run on a schedule and filter on activated\_on downstream. Rows carry Crexi's own stable listing id, so de-duplicating between runs is exact rather than address matching.

**Do I pay per location or per listing?**

Per listing returned. Submitting ten cities that hold 1,000 listings between them costs the same as one city with 1,000. The result cap applies per location rather than per run, so a busy city cannot use up the run and leave the others looking empty. Detail and broker enrichment add one request each per listing but no extra charge, and marker rows are free.

**Is a proxy needed?**

No. Crexi's search API answered a plain client in about 0.24 seconds from an ordinary connection, so the proxy input is off by default and enabling it only adds latency.

### Which actor to choose

Other Actors in the portfolio that pair with this one:

- **Zillow & Redfin Property Scraper** — You need residential comparables alongside the commercial inventory.
- **Business Email & Phone Contact Details Scraper** — You have the brokerages from these listings and now need contact details for outreach.

### Guides & use cases

Written up on **[scrapebench.dev](https://scrapebench.dev)** — the bench that runs and verifies this actor against the live source every night:

- **How-to:** [How to run Crexi Commercial Real Estate Listings & Broker Scraper](https://scrapebench.dev/guides/how-to-crexi-commercial-real-estate-listings-scraper/)
- **Use case:** [Building a County-Level Commercial Inventory Map from Crexi](https://scrapebench.dev/use-cases/crexi-county-inventory-map/)

More actors, coverage and nightly verification results: **[scrapebench.dev](https://scrapebench.dev)**

### Works with AI assistants (MCP)

Callable as an MCP tool, so Claude, Cursor, VS Code Copilot and other MCP clients can run it directly. Grab the config from the [MCP tab](https://apify.com/scrapebench/crexi-commercial-real-estate-listings-scraper/api/mcp) on this page — Apify hosts the server and keeps that snippet current, and OAuth signs you in on first connect, so no API token goes in your config file.

Then just ask:

> "Find retail and office listings in Austin, Texas under $2 million on Crexi, with the county and the listing broker for each."

Locations are free text — a city, a county ('harris county') or a state. Property types must be Crexi's own spellings, e.g. MixedUse and SelfStorage as one word. Runs started this way bill exactly like any other run.

# Actor input Schema

## `query` (type: `string`):

Where to search — a city, county, state or free-text phrase: 'austin texas', 'harris county', 'miami'. Leave blank to sweep the whole catalogue (242,488 listings on 2026-09-07), though Crexi will only page 1,499 deep into any one search, so a location is usually what you want. Kept forever alongside 'Locations' — saved tasks and API callers send it.

## `queries` (type: `array`):

Search many locations in ONE run instead of starting a run per city. Takes the same values as 'Location or keyword'. Combined with it if you fill both, and duplicates are removed. The result cap below applies PER location, so one city cannot eat the whole run and leave the others looking empty.

## `types` (type: `array`):

Crexi's own property types, exactly as it spells them: Land, Retail, Office, Industrial, Multifamily, MixedUse, SpecialPurpose, Hospitality, SelfStorage, MobileHomePark, SeniorLiving. Anything else is rejected by Crexi, so an unrecognised value returns a free row naming the valid ones rather than failing the run. Leave empty for all types.

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

Applied after fetching, because Crexi accepts askingPriceFrom and then ignores it — the total comes back unchanged either way. Setting any band drops listings with no published price ('Call for offers'), since a band is a statement about price that an unpriced listing cannot satisfy.

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

See 'Minimum asking price'. Leave both empty to keep unpriced listings.

## `includeDetails` (type: `boolean`):

Adds the marketing description and Crexi's detail table (zoning, lot size, year built, cap rate, APN and so on) with one extra request per listing. Only the entries Crexi actually publishes come through: it writes paywalled values as runs of asterisks ('Year Built': '\*\*\*\*'), and those are dropped rather than handed to you as text that survives an is-not-null filter.

## `includeBrokers` (type: `boolean`):

Adds the listing broker's name, brokerage, public profile link and how many listings they hold, with one extra request per listing. Crexi publishes no broker email or phone number on this API at any tier, so those are not returned and are not implied.

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

Cap on listings returned PER location, not per run — so a batch cannot silently return only the first rows overall and read as 'these cities have nothing'. Crexi refuses to page beyond 1,499 into any single search, so that is the hard ceiling per location whatever you set; when a search matches more, the run says how many it matched.

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

Optional and off by default. Crexi's search API answered a plain client in about 0.24s from an ordinary connection, so a proxy is not needed and only adds latency (#145: never reach for one before the plain path is proven to fail).

## Actor input object example

```json
{
  "query": "austin texas",
  "queries": [
    "austin texas",
    "san antonio texas"
  ],
  "types": [
    "Retail",
    "Office"
  ],
  "includeDetails": false,
  "includeBrokers": false,
  "maxResults": 200,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

Every matching listing: address, county, asking price, square footage, property type, brokerage and the listing page.

# 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 = {
    "query": "austin texas",
    "queries": [
        "austin texas",
        "san antonio texas"
    ],
    "types": [
        "Retail",
        "Office"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapebench/crexi-commercial-real-estate-listings-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 = {
    "query": "austin texas",
    "queries": [
        "austin texas",
        "san antonio texas",
    ],
    "types": [
        "Retail",
        "Office",
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("scrapebench/crexi-commercial-real-estate-listings-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 '{
  "query": "austin texas",
  "queries": [
    "austin texas",
    "san antonio texas"
  ],
  "types": [
    "Retail",
    "Office"
  ]
}' |
apify call scrapebench/crexi-commercial-real-estate-listings-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,scrapebench/crexi-commercial-real-estate-listings-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/ND7QhOHmaecxyauBi/builds/gbnToDkbCSvi0jMr3/openapi.json
