# Industrial Equipment Price Monitor (`imagiever_ar/industrial-equipment-price-monitor`) Actor

Aggregate used industrial machinery listings from multiple marketplaces into one normalized price dataset.

- **URL**: https://apify.com/imagiever\_ar/industrial-equipment-price-monitor.md
- **Developed by:** [Hernan Borgarino](https://apify.com/imagiever_ar) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 delivered listings

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

## Industrial Equipment Listing Finder

Search public industrial-machinery listings and return the information each marketplace actually exposes — normalized, source-linked, and usable even when the seller leaves fields blank.

This Actor is deliberately simple: **search + normalize + deliver**. It is not a market-price estimator, data-quality judge, or enrichment service.

### Core contract

Search filters are:

- `category`
- `brand`
- `model`
- optional `location` / country

The marketplace search defines the requested category/brand/model scope. Once a listing is found, **missing seller fields do not disqualify it**.

Examples:

- missing year → `year: null`
- missing hours → `hours: null`
- missing model → `model: null`
- unknown condition → `condition: "unknown"`
- no numeric price → `price: null`, `has_price: false`

Every delivered listing keeps its source URL so a user or downstream agent can inspect the original publication.

#### Country is the only hard post-search filter

If a country is requested and a listing explicitly declares another country, the listing is rejected.

If the marketplace returns a listing inside the requested search but the listing itself does not declare a country, the Actor does **not** invent one: `country` stays `null` and the listing may still be delivered.

This prevents obvious wrong-country results without pretending marketplace data is more complete than it is.

### Price on request / consultar precio

Listings without a public numeric price are still useful and are still delivered.

If a source says **Contact seller**, **Price on request**, **Consultar precio**, **Sob consulta**, or similar, the Actor preserves that information in `price_text`:

```json
{
  "price": null,
  "currency": null,
  "has_price": false,
  "price_text": "Consultar precio",
  "listing_url": "https://example.com/listing/123"
}
```

No price is invented. A bare `$` symbol is not assumed to mean USD; when the numeric amount is readable but the currency is ambiguous, the Actor keeps the numeric `price`, sets `has_price: true`, and leaves `currency: null`.

### Output fields

A listing record can contain:

- `brand`
- `model`
- `categoryDeclared`
- `year`
- `hours`
- `condition` — `new`, `used`, or `unknown`
- `price`
- `currency`
- `has_price` — true when the source publishes a numeric price, even if currency is unavailable
- `price_text`
- `price_usd` — only when a verified conversion exists; otherwise `null`
- `fx_rate_to_usd`
- `fx_rate_date`
- `country` — ISO 3166-1 alpha-2 when resolvable from declared location
- `location`
- `sku`
- `listing_url`
- `source`
- `sources` — when conservative cross-source deduplication merges an obvious duplicate
- `source_listings`
- `scraped_at`
- `specsRaw` — additional technical facts after contact/PII stripping

Missing marketplace facts remain `null` or `unknown`. That is expected behavior, not an error.

### Coverage levels

#### Local coverage — default

Queried first when the user asks for a country:

- **Plant & Equipment**
- **Mercado Libre**

`sources: ["auto"]` selects both, providing redundancy for the Apify health prefill.

#### Global market reference — optional

- **Machineryline**
- **Machinio**

These sources are useful for global supply/reference data. Local inventory is not guaranteed.

#### Optional / noisy

- **eBay Heavy Equipment** is available only when explicitly selected or via `all`. It remains outside defaults because of search noise and intermittent source errors.

### Source completeness observed during QA

This is a small observed sample, **not a guarantee** for future listings:

| Source | Coverage role | Sample | Year | Hours | Public price | Country |
|---|---|---:|---:|---:|---:|---:|
| Plant & Equipment | Local | 5 | 100% | 100% | 100% | 100% |
| Mercado Libre | Local | 5 | 100% | 100% | 40% | 100% |
| Machineryline | Global reference | 5 | 100% | 100% | 80% | 100% |
| Machinio | Global reference | 5 | 100% | 100% | 60% | 100% |
| eBay Heavy Equipment | Optional/noisy | 5 valid machinery listings | 100% | 100% | 100% | 100% |

Each run also stores actual per-source completeness in `RUN_SUMMARY`. Completeness is **diagnostic only**; it never decides whether an otherwise valid marketplace listing is worth returning.

### Pricing

The custom event is:

```text
result = US$0.003 per delivered listing
```

**Every delivered listing is charged the same**, whether every seller field is present or several fields are `null`.

A normalized record with nulls is still a complete result for this Actor: it represents the information the marketplace actually provided. Only `run_diagnostic` records are free.

Example: a run delivers 10 listings:

- 6 have numeric prices
- 4 say `Consultar precio` or omit price

Billing is:

```text
10 delivered listings × US$0.003 = US$0.030
```

`apify-default-dataset-item` must remain disabled. The final log and `RUN_SUMMARY` report:

- delivered listings
- charged listings
- listings with numeric price
- listings without numeric price
- free diagnostics
- expected custom-event charge

This lets users reconcile billing without opening the dataset.

#### Exact Apify event description

Use this description for the `result` event in Apify Console:

> One delivered machinery listing record. Missing seller-declared fields, including numeric price, may be null. Run diagnostics are free.

### Example input

The Store prefill is intentionally small and redundant:

```json
{
  "category": "excavator",
  "brand": "Caterpillar",
  "location": "Argentina",
  "sources": ["auto"],
  "maxResults": 5,
  "maxPagesPerSource": 1,
  "maxDetailsPerSource": 5,
  "maxRuntimeSecs": 180
}
```

The prefill queries both local/default sources instead of depending on a single marketplace.

### Example outputs

Numeric price:

```json
{
  "record_type": "listing",
  "brand": "Caterpillar",
  "model": "320",
  "year": 2023,
  "hours": 900,
  "condition": "used",
  "price": 125000,
  "currency": "USD",
  "has_price": true,
  "price_text": null,
  "country": "AR",
  "location": "General San Martín, Argentina",
  "source": "mercadolibre",
  "listing_url": "https://...",
  "scraped_at": "2026-09-24T00:00:00.000Z"
}
```

Partial seller data / price on request:

```json
{
  "record_type": "listing",
  "brand": "Komatsu",
  "model": null,
  "year": 2012,
  "hours": 4800,
  "condition": "used",
  "price": null,
  "currency": null,
  "has_price": false,
  "price_text": "Price on request",
  "country": "ES",
  "location": "Vigo, Spain",
  "source": "machineryline",
  "listing_url": "https://...",
  "scraped_at": "2026-09-24T00:00:00.000Z"
}
```

### Runtime and resilience

- Internal runtime budget: 180 seconds by default.
- When the internal budget is reached, the Actor closes cleanly with what it already found.
- Zero results is a valid run: the default dataset receives a free `record_type="run_diagnostic"` item, so the dataset is never intentionally empty.
- Empty/invalid input also ends `SUCCEEDED` with a free diagnostic record.
- 403, 429, challenge/interstitial or blocked pages are skipped and reported. **No CAPTCHA solving, session rotation, residential proxying, or anti-bot evasion is used.**
- Brand/model/category are search filters, not detail-page completeness gates.

### Privacy and source policy

- Public marketplace pages only.
- No login, account, checkout, messaging, or private areas.
- No anti-bot evasion.
- Obvious email, phone, WhatsApp links and contact/address fields are stripped from free text/specifications.
- Every listing includes its public source URL.

### Known limitations

- Marketplace listings are user-generated and often incomplete or inconsistent. The Actor preserves that reality with `null` / `unknown` instead of guessing.
- Search quality is ultimately constrained by each marketplace's own search/indexing behavior.
- Some marketplaces expose country filters more reliably than others. Explicit wrong-country listings are rejected; undeclared country stays `null`.
- `price_usd` is only populated when a verified conversion exists. No exchange rate is guessed.
- Cross-source deduplication is intentionally conservative; uncertain duplicates remain separate records.
- Source layouts can change. The release protocol uses regression tests plus repeated cloud-prefill runs before publication/rebuild.

### Keywords

Industrial equipment, heavy machinery, maquinaria usada, maquinaria pesada, excavadoras, retroexcavadoras, Caterpillar, Komatsu, maquinaria industrial, máquinas usadas, equipamentos pesados, máquinas usadas, escavadeiras, preços, listings, equipment API.

# Changelog

This Actor's version history is a separate document: https://apify.com/imagiever\_ar/industrial-equipment-price-monitor/changelog.md

# Actor input Schema

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

Optional natural-language search text. Used only to build the marketplace search.

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

Optional structured category search filter. Missing category data on a returned listing does not cause rejection.

## `brand` (type: `string`):

Optional brand search filter. Missing brand data on a returned listing does not cause rejection.

## `model` (type: `string`):

Optional model search filter. Missing model data on a returned listing does not cause rejection.

## `location` (type: `string`):

Optional country/geographic search filter. Country is the only post-search pass/fail field: a listing with an explicitly conflicting country is rejected; unknown country remains null.

## `sources` (type: `array`):

auto = local coverage first (Plant & Equipment + Mercado Libre); global = Machineryline + Machinio; all = local + global + eBay; or select individual sources.

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

Maximum normalized records returned across all sources.

## `maxPagesPerSource` (type: `integer`):

Search pagination cap per marketplace.

## `maxDetailsPerSource` (type: `integer`):

Safety/cost cap for listing detail pages per source. Start around 8-10 while testing; raise only when broader coverage is needed.

## `maxRuntimeSecs` (type: `integer`):

Actor stops accepting new work before this limit so it can close cleanly instead of hitting the platform timeout.

## Actor input object example

```json
{
  "query": "excavadora Caterpillar usada",
  "category": "excavator",
  "brand": "Caterpillar",
  "location": "Argentina",
  "sources": [
    "auto"
  ],
  "maxResults": 5,
  "maxPagesPerSource": 1,
  "maxDetailsPerSource": 5,
  "maxRuntimeSecs": 180
}
```

# Actor output Schema

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

Default dataset items produced by this run.

# 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 = {
    "category": "excavator",
    "brand": "Caterpillar",
    "location": "Argentina",
    "sources": [
        "auto"
    ],
    "maxResults": 5,
    "maxPagesPerSource": 1,
    "maxDetailsPerSource": 5,
    "maxRuntimeSecs": 180
};

// Run the Actor and wait for it to finish
const run = await client.actor("imagiever_ar/industrial-equipment-price-monitor").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 = {
    "category": "excavator",
    "brand": "Caterpillar",
    "location": "Argentina",
    "sources": ["auto"],
    "maxResults": 5,
    "maxPagesPerSource": 1,
    "maxDetailsPerSource": 5,
    "maxRuntimeSecs": 180,
}

# Run the Actor and wait for it to finish
run = client.actor("imagiever_ar/industrial-equipment-price-monitor").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 '{
  "category": "excavator",
  "brand": "Caterpillar",
  "location": "Argentina",
  "sources": [
    "auto"
  ],
  "maxResults": 5,
  "maxPagesPerSource": 1,
  "maxDetailsPerSource": 5,
  "maxRuntimeSecs": 180
}' |
apify call imagiever_ar/industrial-equipment-price-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,imagiever_ar/industrial-equipment-price-monitor"
        }
    }
}
```

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/e20qrbEAhatMjfRLC/builds/uFOd5Wp9edgARP7Xq/openapi.json
