# Controller.com Scraper — Aircraft Listings & Price Monitor (`crawloop/controller-com-scraper`) Actor

Scrape Controller.com aircraft listings for brokers and buyers: asking prices, total time, registration, serials, dealer phones, and images. Monitor new inventory and price changes. Listings or details mode.

- **URL**: https://apify.com/crawloop/controller-com-scraper.md
- **Developed by:** [Andrej Kiva](https://apify.com/crawloop) (community)
- **Categories:** E-commerce, Lead generation, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.29 / 1,000 scraped controller.com listings

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/platform/actors/running/actors-in-store#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

## Controller.com Scraper — Aircraft Listings & Price Monitor

> **Crawloop Aviation & Sandhills Classifieds** — Controller.com aircraft asking prices, total time, registration, dealer phones, and inventory change alerts. Pair with AeroExpo for OEM / MRO catalogs.

| Controller.com (aircraft) | MachineryTrader (US heavy) | TractorHouse (farm) | TruckPaper (trucks) | AeroExpo (aeronautical catalog) |
| :--- | :--- | :--- | :--- | :--- |
| Controller.com Scraper ◄── you are here | [MachineryTrader Scraper](https://apify.com/crawloop/machinerytrader-scraper) | [TractorHouse Scraper](https://apify.com/crawloop/tractorhouse-scraper) | [TruckPaper Scraper](https://apify.com/crawloop/truckpaper-scraper) | [AeroExpo Scraper](https://apify.com/crawloop/aeroexpo-scraper) |

> **Disclaimer:** Unofficial tool — not affiliated with, sponsored by, or endorsed by Controller.com, Sandhills Global, or their brands. Data is read from publicly accessible pages only. You are responsible for complying with applicable law and site terms. No warranty on accuracy or availability.

### What this Controller.com scraper does

This **Controller.com scraper** extracts aircraft for-sale, for-lease, and fractional-ownership listings into a clean Apify dataset: asking price, make, model, year, total time, registration, serial number, engine times, dealer phone, location, and images. Built as a Controller.com API alternative for aircraft brokers, buyers’ agents, lessors, appraisers, and aviation data teams who need jets, turboprops, piston aircraft, and helicopters in **Python**, **Node.js**, or **MCP** — export JSON / CSV / Excel without manual browsing.

Run `listings` for fast catalog pulls (retail cards already include dealer phone and aviation specs) or `details` for PDP canonical URL, breadcrumbs, and video. Enable **`monitorMode`** on an Apify Schedule to track **new inventory** and **price changes** per dealer, category, or search.

### When to use

- Broker competitive intelligence — watch a rival `AccountCRMID` inventory daily
- Asking-price comps for Citation, King Air, SR22, Robinson, and Gulfstream listings
- Acquisition shortlists by category, year, total time, seats, state, or registration
- Outreach lists with published dealer phones (no details mode required for most retail cards)
- Join used-aircraft asking prices with OEM catalogs from [AeroExpo Scraper](https://apify.com/crawloop/aeroexpo-scraper)

#### When not to use

- You need authenticated sold / valuation figures from Controller Values (login-gated)
- You only need aeronautical **product catalogs** (avionics, GSE, UAV) — start with [AeroExpo Scraper](https://apify.com/crawloop/aeroexpo-scraper)
- You need US construction or farm boards — use [MachineryTrader Scraper](https://apify.com/crawloop/machinerytrader-scraper) or [TractorHouse Scraper](https://apify.com/crawloop/tractorhouse-scraper)

### Aviation + Sandhills workflow

```
Used aircraft asking prices         Catalog / other Sandhills boards
───────────────────────────         ────────────────────────────────

  Controller.com Scraper ◄── you are here
       │ dealer phones, total time, registration, monitor deltas
       ▼
  AeroExpo Scraper ───────────────► OEM / MRO product catalogs
  MachineryTrader Scraper ────────► US heavy equipment
  TractorHouse / TruckPaper ──────► farm and commercial trucks
```

### Key features

- **Search URL or filters** — paste a search / dealer inventory / category URL, or build one from manufacturer, category, keywords, state, country, condition, seats, registration, and `AccountCRMID`
- **Two modes** — `listings` (fast SSR cards with phone + specs) and `details` (canonical URL, video, breadcrumbs)
- **Monitor mode** — seed fingerprints, then emit only `new` / `price_change` rows; optional Telegram alerts
- **Aviation specs** — total time, registration, serial, engine times, seats, flight rules, ADS-B / WAAS / SVT, extra specs preserved
- **Dealer phones on cards** — outreach fields without visiting every PDP
- **Cloudflare-ready** — `curl_cffi` Chrome TLS; Apify Residential (US) recommended
- **Priced-only filter** — skip Call-for-price and zero-price rows
- **Listing types** — `For Retail`, `For Lease`, and `For Fractional Ownership`

### Input

| Field | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `startUrls` | Array | Cessna sample | Search, category, dealer inventory, or detail URLs |
| `searchQuery` | String | — | Keywords (make, model, registration, serial) |
| `listingType` | String | `For Retail` | `For Retail`, `For Lease`, or `For Fractional Ownership` |
| `manufacturer` | String | — | e.g. `CESSNA`, `CIRRUS`, `BEECHCRAFT` (comma-separated) |
| `category` | String | — | Category id (`3` jets, `8` turboprop, `6` piston single, `7` turbine helicopters) |
| `accountCrmId` | String | — | Dealer `AccountCRMID` inventory watch |
| `state` / `country` | String | — | Location filters when building a search URL |
| `condition` | String | — | `New` or `Used` |
| `registrationNumber` | String | — | Exact N-number / registration lookup |
| `runMode` | String | `listings` | `listings` or `details` |
| `pricedOnly` | Boolean | `false` | Keep only rows with a published price |
| `maxItems` | Integer | `100` | Emit budget (seed run: fingerprint budget) |
| `maxPagesPerUrl` | Integer | `10` | Pages per start URL (~28 listings/page) |
| `monitorMode` | Boolean | `false` | Delta tracking via named KV store |
| `monitorStoreName` | String | `controller-com-monitor-state` | Distinct name per watch target |
| `proxyConfiguration` | Object | US Residential prefill | Apify proxy settings |

```json
{
  "searchQuery": "Cirrus SR22",
  "listingType": "For Retail",
  "runMode": "listings",
  "maxItems": 50,
  "maxPagesPerUrl": 3,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": ["RESIDENTIAL"],
    "apifyProxyCountry": "US"
  }
}
```

```json
{
  "category": "3",
  "yearMin": 2015,
  "totalTimeMax": 4000,
  "pricedOnly": true,
  "sort": "priceHighLow",
  "maxItems": 100
}
```

```json
{
  "accountCrmId": "8309467",
  "runMode": "listings",
  "monitorMode": true,
  "monitorStoreName": "controller-iams-jets",
  "maxItems": 500,
  "maxPagesPerUrl": 20
}
```

With `monitorMode: true`, the first run seeds fingerprints and writes **no** dataset rows. Later scheduled runs emit only `changeType: "new"` or `"price_change"`.

### Output

| Field | Description |
| :--- | :--- |
| `id` | Listing id |
| `title` / `make` / `model` / `year` | Aircraft identity |
| `price` / `currency` / `priceFormatted` / `priceOnRequest` | Asking price when published |
| `totalTime` / `totalTimeHours` / `registrationNumber` / `serialNumber` | Airframe identity and hours |
| `engine1MakeModel` / `engine1Time` / `seats` / `flightRules` | Common aviation specs |
| `additionalSpecs` | Any other published spec (winglets, APU, programs, …) |
| `dealerName` / `dealerPhone` / `dealerContact` | Seller contact |
| `accountCrmId` | Dealer account key for inventory watch URLs |
| `location` / `city` / `state` / `country` | Aircraft location |
| `images` / `imageCount` | Photo URLs |
| `changeType` / `previousPrice` / `monitoredAt` | Monitor deltas only |

```json
{
  "id": "258432389",
  "title": "2016 CESSNA CITATION LATITUDE",
  "make": "CESSNA",
  "model": "CITATION LATITUDE",
  "year": "2016",
  "price": null,
  "currency": "USD",
  "priceFormatted": "Call for price",
  "priceOnRequest": true,
  "condition": "Used",
  "category": "Jet Aircraft",
  "totalTimeHours": 3552,
  "serialNumber": "680A-0067",
  "registrationNumber": "N518KB",
  "engine1MakeModel": "PRATT & WHITNEY PW306D1",
  "dealerName": "International Aircraft Marketing & Sales",
  "dealerPhone": "(941) 961-4245",
  "accountCrmId": "8309467",
  "location": "Orlando, Florida",
  "url": "https://www.controller.com/listing/for-sale/258432389/2016-cessna-citation-latitude-jet-aircraft"
}
```

### Use cases

- Aircraft brokers — monitor new and competitor listings by category, price band, or region
- Market research — track asking prices across manufacturers, years, and aircraft categories
- Aviation finance & leasing — source retail, lease, and fractional-ownership inventory
- Aircraft valuation tools — build comparable datasets from published listings
- Buyer's agents — shortlist aircraft matching budget, hours, seats, and location

### Integration examples

#### Node.js

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });

const run = await client.actor('crawloop/controller-com-scraper').call({
  manufacturer: 'CESSNA',
  category: '3',
  runMode: 'listings',
  maxItems: 40,
  pricedOnly: true,
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
```

#### Python

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_APIFY_TOKEN")
run = client.actor("crawloop/controller-com-scraper").call(run_input={
    "searchQuery": "Cirrus SR22",
    "runMode": "listings",
    "maxItems": 40,
})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item["title"], item.get("priceFormatted"), item.get("dealerPhone"))
```

#### cURL

```bash
curl "https://api.apify.com/v2/acts/crawloop~controller-com-scraper/run-sync-get-dataset-items?token=YOUR_APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"manufacturer\":\"CESSNA\",\"category\":\"3\",\"maxItems\":25,\"runMode\":\"listings\"}"
```

### MCP and AI assistants

Use this Actor from AI tools via [Apify MCP](https://docs.apify.com/platform/integrations/mcp).
Connect your Apify account, then call this Actor by its Store ID `crawloop/controller-com-scraper`.

Example prompts:

- "Run Controller.com Scraper for Cessna jets, max 40, priced only, and return title, price, totalTimeHours, registrationNumber, dealerPhone as JSON"
- "Monitor dealer AccountCRMID 8309467 on Controller.com and list only new or price-changed rows"
- "Scrape Controller.com listings for Cirrus SR22, then look up avionics catalogs with AeroExpo Scraper"

### Suite next step

After used-aircraft asking prices, pull aeronautical product catalogs (avionics, GSE, UAV) with [AeroExpo Scraper](https://apify.com/crawloop/aeroexpo-scraper), or US heavy equipment comps with [MachineryTrader Scraper](https://apify.com/crawloop/machinerytrader-scraper).

### FAQ

**Is this a Controller.com API?**\
There is no official public bulk API for most users. This Actor is a structured scrape / API alternative that returns a dataset you can call from Python, Node.js, cURL, or MCP.

**Do I need details mode for dealer phones?**\
Usually no — retail cards already expose `dealerPhone`, location, total time, and registration. Use `details` when you need canonical URL, breadcrumbs, or video.

**How do I watch one dealer?**\
Set `accountCrmId` (from any listing card) or paste a dealer inventory search URL, enable `monitorMode`, pick a unique `monitorStoreName`, and schedule the Actor.

**Why is `price` missing on some listings?**\
Sellers often list "Call for price". Those rows get `priceOnRequest: true` and no numeric `price`. Turn on `pricedOnly` to skip them.

**Which proxy should I use?**\
Apify Residential with `apifyProxyCountry: "US"` is recommended — the site sits behind Cloudflare.

**Can I search by N-number?**\
Yes — set `registrationNumber` (exact match) or put the N-number in `searchQuery`.

**Does Controller EMEA use a different catalog?**\
The `.co.uk` front is the same Sandhills inventory. Switch `baseUrl` if you need that host; filters and fields are the same.

### Related Actors

| Actor | Role |
| :--- | :--- |
| [AeroExpo Scraper](https://apify.com/crawloop/aeroexpo-scraper) | Aeronautical product catalogs, specs, PDF datasheets |
| [MachineryTrader Scraper](https://apify.com/crawloop/machinerytrader-scraper) | US Sandhills heavy equipment + dealer monitor |
| [TractorHouse Scraper](https://apify.com/crawloop/tractorhouse-scraper) | US / EU Sandhills farm equipment |
| [TruckPaper Scraper](https://apify.com/crawloop/truckpaper-scraper) | US commercial trucks and trailers |

# Actor input Schema

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

Search results, category SEO pages, dealer inventory URLs (AccountCRMID), or direct listing detail pages.

## `searchQuery` (type: `string`):

Keyword search across make, model, registration, and serial (e.g. Cirrus SR22, N518KB).

## `listingType` (type: `string`):

Used when building a search URL from filters (or when startUrls is empty).

## `manufacturer` (type: `string`):

One or more manufacturers, comma-separated (e.g. CESSNA, CIRRUS, BEECHCRAFT, GULFSTREAM).

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

Controller.com category id or name. Leave empty to search all aircraft types.

## `accountCrmId` (type: `string`):

Watch a single dealer inventory by AccountCRMID (from listing cards).

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

Location filter when building a search URL (e.g. Florida, Texas, Ontario).

## `country` (type: `string`):

Country filter when building a search URL (e.g. USA, Canada).

## `condition` (type: `string`):

New or Used (server-side filter).

## `registrationNumber` (type: `string`):

Exact registration / N-number lookup (e.g. N518KB).

## `serialNumber` (type: `string`):

Exact airframe serial lookup.

## `wifiEquipped` (type: `boolean`):

Restrict to listings Controller.com marks as WiFi equipped.

## `sort` (type: `string`):

Result order for built search URLs.

## `priceMin` (type: `integer`):

Keep listings at or above this asking price. Call-for-price rows are kept unless pricedOnly is on.

## `priceMax` (type: `integer`):

Keep listings at or below this asking price.

## `yearMin` (type: `integer`):

Keep aircraft manufactured in or after this year.

## `yearMax` (type: `integer`):

Keep aircraft manufactured in or before this year.

## `totalTimeMin` (type: `integer`):

Keep aircraft with airframe total time at or above this many hours.

## `totalTimeMax` (type: `integer`):

Keep aircraft with airframe total time at or below this many hours.

## `seatsMin` (type: `integer`):

Keep aircraft with at least this many seats. Exact min=max is applied server-side.

## `seatsMax` (type: `integer`):

Keep aircraft with at most this many seats.

## `yearPaintedMin` (type: `integer`):

Keep listings whose published year-painted is at or after this year.

## `yearPaintedMax` (type: `integer`):

Keep listings whose published year-painted is at or before this year.

## `yearInteriorMin` (type: `integer`):

Keep listings whose published interior-refresh year is at or after this year.

## `yearInteriorMax` (type: `integer`):

Keep listings whose published interior-refresh year is at or before this year.

## `baseUrl` (type: `string`):

Site origin for built search URLs. Default US Controller.com; EMEA front is the same catalog.

## `runMode` (type: `string`):

listings = fast card fields (price, total time, registration, dealer phone, images). details = also visit each PDP for canonical URL, breadcrumbs, and video.

## `pricedOnly` (type: `boolean`):

Skip Call-for-price / zero-price rows.

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

Maximum dataset rows to emit (in monitor seed runs: max listings to fingerprint).

## `maxPagesPerUrl` (type: `integer`):

Maximum pagination pages per search / dealer inventory URL (28 listings per page).

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

Apify Residential Proxies recommended — Controller.com uses Cloudflare.

## `concurrencyLimit` (type: `integer`):

Parallel PDP fetches in details mode.

## `monitorMode` (type: `boolean`):

Delta tracking for category, search, or dealer inventory URLs. First run seeds a named Key-Value store (no dataset rows / alerts). Later runs output only new listings and price changes. Pair with an Apify Schedule.

## `monitorStoreName` (type: `string`):

Named Key-Value store for fingerprints across scheduled runs. Use a distinct name per watch target (dealer or category).

## `telegramToken` (type: `string`):

Bot token for real-time alerts on new listings and price changes (monitorMode only).

## `telegramChatId` (type: `string`):

Telegram user or group chat ID that receives monitor alerts.

## `resetMonitorState` (type: `boolean`):

Clear stored listing fingerprints before this run (forces a fresh seed run).

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://www.controller.com/listings/search?ListingType=For%20Retail&Manufacturer=CESSNA"
    }
  ],
  "searchQuery": "Cessna Citation",
  "listingType": "For Retail",
  "manufacturer": "CESSNA, CIRRUS",
  "accountCrmId": "8309467",
  "state": "Florida",
  "country": "USA",
  "registrationNumber": "N518KB",
  "serialNumber": "680A-0067",
  "wifiEquipped": false,
  "baseUrl": "https://www.controller.com",
  "runMode": "listings",
  "pricedOnly": false,
  "maxItems": 100,
  "maxPagesPerUrl": 10,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  },
  "concurrencyLimit": 5,
  "monitorMode": false,
  "monitorStoreName": "controller-com-monitor-state",
  "resetMonitorState": false
}
```

# Actor output Schema

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

Default dataset items: aircraft listings with prices, total time, registration, and dealer phones.

# 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 = {
    "startUrls": [
        {
            "url": "https://www.controller.com/listings/search?ListingType=For%20Retail&Manufacturer=CESSNA"
        }
    ],
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "US"
    },
    "monitorStoreName": "controller-com-monitor-state"
};

// Run the Actor and wait for it to finish
const run = await client.actor("crawloop/controller-com-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 = {
    "startUrls": [{ "url": "https://www.controller.com/listings/search?ListingType=For%20Retail&Manufacturer=CESSNA" }],
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "US",
    },
    "monitorStoreName": "controller-com-monitor-state",
}

# Run the Actor and wait for it to finish
run = client.actor("crawloop/controller-com-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 '{
  "startUrls": [
    {
      "url": "https://www.controller.com/listings/search?ListingType=For%20Retail&Manufacturer=CESSNA"
    }
  ],
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  },
  "monitorStoreName": "controller-com-monitor-state"
}' |
apify call crawloop/controller-com-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,crawloop/controller-com-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/jnRhDezTOrdw5kf1w/builds/V0fOzU3qFsWJuZP8e/openapi.json
