# LocalHarvest Farms and Farmers Markets Scraper (`automation-lab/localharvest-farms-markets-directory`) Actor

Search LocalHarvest farms, farmers markets, CSAs and local-food listings. Export public locations, products, seasons, contacts and profile URLs for directory refreshes.

- **URL**: https://apify.com/automation-lab/localharvest-farms-markets-directory.md
- **Developed by:** [Automation Lab](https://apify.com/automation-lab) (community)
- **Categories:** Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.40 / 1,000 item processeds

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/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

## LocalHarvest Farms and Farmers Markets Scraper

Refresh local-food directories with structured public LocalHarvest farms and farmers markets profiles. Search by location, listing category, and optional text, or export a supplied list of profile URLs. Each row includes business identity, location, public contact links, description, crops, seasons, and the original profile URL when available.

### Who is this for?

Food buyers can build a shortlist of nearby producers. Local directory editors can refresh listing details. Researchers can compare farm, CSA, farmers-market, and co-op coverage across locations. The Actor collects a snapshot, not a historical archive or a change-alert service.

### Why use this Actor?

- Export full profiles rather than only short search snippets.
- Use stable LocalHarvest IDs to join recurring snapshots.
- Collect all displayed seasonal crop tabs without browser interaction.
- Deduplicate repeated profile URLs within a run.
- Keep source links alongside business information for verification.

### Getting started

1. Enter a city and state, such as `Portland, OR`, or a US ZIP code.
2. Choose a listing type, or keep `all`.
3. Set a small `maxItems` for your first run.
4. Run the Actor and inspect the default dataset.
5. Download JSON, CSV, or Excel using Apify's dataset export controls.

Example search:

```json
{"location":"Portland, OR","listingType":"farms","maxItems":20,"maxPages":10}
```

Direct profile example:

```json
{"profileUrls":["https://www.localharvest.org/house-in-the-woods-csa-M171"],"maxItems":1}
```

Direct profile URLs cannot be combined with search filters. This is validated, not silently ignored.

### Input parameters

| Parameter | Meaning |
| --- | --- |
| `location` | City/state or ZIP passed to LocalHarvest's proximity search; not a strict city boundary. |
| `searchTerm` | Optional native search text; maximum 120 characters. Matching and ranking are controlled by LocalHarvest, not an exact-phrase contract. |
| `listingType` | `all`, `farms`, `markets`, `csa`, `groceries`, or `other`. Default `all`. |
| `profileUrls` | Up to 1,000 public profile URLs ending in `-M` and a numeric ID. No subpages, fragments, or query strings. |
| `maxItems` | Global unique output-profile limit, default 20, range 1–10,000. Zero/unlimited are not supported. |
| `maxPages` | Search-result page ceiling, default 10, range 1–1,000. Ignored for direct profiles. |

Provide location and/or searchTerm for discovery, or profileUrls for direct export. Search may finish before maxItems when the source is exhausted, maxPages is reached, or the run times out. Limit ceilings do not promise exhaustive national coverage.

### Extracted data

| Field | Contents |
| --- | --- |
| `listingId`, `name`, `profileUrl` | Stable source listing ID, displayed name, canonical profile URL. |
| `listingType`, `location` | Displayed profile category and human-readable location. |
| `streetAddress`, `city`, `state`, `postalCode`, `country` | Public address components when exposed in source structured data. |
| `description` | Full profile description, with whitespace normalized. |
| `phone`, `email`, `website` | Public business contact fields when available. Email is only collected from an exposed mailto link, never inferred. |
| `contactUrl` | LocalHarvest's public contact-form URL, not an email address. No form is submitted. |
| `products`, `seasons` | Unique product labels across displayed seasonal tabs, plus season labels. |
| `csaSeason`, `marketChannels` | Displayed CSA operating season and selling channels. |
| `lastUpdated`, `scrapedAt` | Source update label and UTC collection timestamp. |

Unavailable scalar fields are null; absent list fields are empty arrays. The profile category may differ from the selected search category: a CSA search can return a Family Farm offering shares. Co-ops are included when LocalHarvest classifies them as groceries.

### Output example

A shortened example from a public farm profile; the full dataset also contains the remaining address and contact fields:

```json
{
  "listingId": "20287",
  "name": "Hood River Organic",
  "profileUrl": "https://www.localharvest.org/hood-river-organic-M20287",
  "listingType": "Family Farm",
  "location": "Hood River, Oregon",
  "city": "Hood River",
  "state": "OR",
  "csaSeason": "Year round",
  "marketChannels": ["Online Store", "CSA", "Wholesale"],
  "seasons": ["Winter", "Spring", "Summer", "Fall"]
}
```

### How much does it cost to export LocalHarvest profiles?

Pay-per-event pricing charges a one-time **$0.005 start fee** and one `item` event for each profile saved. At the BRONZE tier, the item price is **$0.004004 per profile**. Other plan tiers are shown in the Pricing tab.

Example BRONZE run totals, including the start fee:

| Exported records | Estimated run total |
| --- | --- |
| 1 | $0.009004 |
| 10 | $0.04504 |
| 100 | $0.4054 |

An empty search has only the start fee. Invalid input is rejected before the start charge. There is no separate event charge for products, seasonal labels, or contact fields within a profile.

### Search scope and pagination

The Actor follows the source's next-page links, up to maxPages or maxItems. With only a location and listingType all, LocalHarvest serves a mixed-category overview (currently about eight cards) without pagination. Choose a specific type for deeper paginated results; all does not mean an exhaustive combined directory. Source ordering can prioritize supporters or listings outside the named city. Do not treat the first result as the closest business or infer a geographic radius. Text search semantics are source-defined. No client-side exact-name or product filter is applied.

The products array unions all displayed seasonal tabs; it does not preserve a product-to-season mapping or verify current stock. Source update dates can be old or self-reported. Confirm opening hours, share availability, and contact details before acting on them.

### Reliability and limitations

Search and profile requests use an included US residential proxy with bounded fresh-session recovery. The company pays the proxy cost; you do not supply an account, paid data API, or cookies. Detail concurrency is limited to three and transient failures have at most three attempts, each with a hard timeout.

Unrecognized pages, blocked responses, missing profiles, and exhausted network retries fail the run honestly. Rows saved before a failure remain partial results, not a successful complete export. Malformed URLs and unsupported filter combinations fail early. Duplicate input URLs are exported once, and maxItems is a global run limit.

### Integrations

Schedule recurring snapshots in Apify and compare rows downstream by `listingId`. Send new dataset rows to Google Sheets, Make, n8n, or a database to maintain a local-food directory. Use a dataset webhook to start your own comparison workflow after successful completion. Scheduling and integrations are platform features; the Actor itself does not send alerts or maintain history.

### API access

Start one run with your own Apify API token:

```bash
curl -X POST 'https://api.apify.com/v2/acts/automation-lab~localharvest-farms-markets-directory/runs' \
  -H "Authorization: Bearer $APIFY_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"location":"Portland, OR","listingType":"farms","maxItems":20}'
```

JavaScript:

```javascript
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('automation-lab/localharvest-farms-markets-directory').call({
  location: 'Portland, OR', maxItems: 20
});
const { items } = await client.dataset(run.defaultDatasetId).listItems({ limit: 20 });
console.log(items);
```

Python:

```python
import os
from apify_client import ApifyClient
client = ApifyClient(os.environ['APIFY_TOKEN'])
run = client.actor('automation-lab/localharvest-farms-markets-directory').call(
    run_input={'location': 'Portland, OR', 'maxItems': 20})
rows = client.dataset(run['defaultDatasetId']).list_items(limit=20).items
print(rows)
```

### MCP access

Select this stable Actor subset in Claude Code:

```bash
claude mcp add --transport http apify \
  'https://mcp.apify.com?tools=automation-lab/localharvest-farms-markets-directory'
```

For Claude Desktop, Cursor, and VS Code clients that support remote HTTP MCP:

```json
{"mcpServers":{"apify":{"url":"https://mcp.apify.com?tools=automation-lab/localharvest-farms-markets-directory"}}}
```

Authenticate using your client's supported Apify flow. Discover actual tool names and arguments through scoped `tools/list`; selecting an Actor also exposes run/data helper tools, not exactly one tool.

Example prompt: “Find up to 20 LocalHarvest farm profiles near Portland, Oregon, and show names, locations, and profile links.” Only start a run when authorized. Retain the returned run ID and storage IDs; they are metadata, not result rows. If still running after the server wait (at most 45 seconds), check the same run with bounded backoff, for example 2, 4, then 8 seconds capped at 10, with a total 120-second deadline. Configure the client timeout above the selected server wait plus transport margin. On timeout recover that known run, never restart just to poll. At the deadline report pending with its ID.

After success, fetch bounded dataset pages with explicit limit, offset, and needed fields. A useful consumer budget is 20 source rows per page, at most 100 rows and 64 KiB of serialized UTF-8 output admitted to model context, whichever comes first. Enforce the byte budget host-side, including oversized rows; disclose omissions and the continuation offset. If your client cannot intercept oversized responses, do not claim a hard byte guarantee. Failed-run rows are partial. Keep full exports outside model context. No universal token-saving guarantee is made.

### Legality and privacy

Collect only public information for legitimate research and directory maintenance. Public contact details can still be personal data: comply with applicable privacy laws, source terms, retention policies, and outreach rules. This Actor does not grant marketing consent or access private accounts, reveal hidden emails, submit contact forms, or crawl external business sites.

No AI model is used during extraction or to generate dataset content. Run datasets and logs remain in your Apify account under your storage retention settings until expired or deleted; delete them through Apify when no longer needed. No cross-run cache is maintained. Proxy credentials are protected runtime configuration, never public input. Apify hosts execution/storage and supplies proxy routing; our private diagnostic service receives only sanitized failure reports. This Actor is independently developed and is not affiliated with or endorsed by LocalHarvest. For support, open an issue from the Actor's Apify page.

Failed operations send sanitized diagnostic input, exceptions, and Actor/build/run IDs to our private GlitchTip service for repair. Secret fields and URL queries are removed, and reports are retained for 30 days.

### FAQ and troubleshooting

**Why are some contacts missing?** LocalHarvest may expose only a contact form. Its link is returned as contactUrl; no email is guessed.

**Why did I receive fewer profiles than maxItems?** The source may have fewer matches, or maxPages stopped pagination. A failed or timed-out run can leave partial rows. Check its status and logs.

**Why are listings outside my city?** Native search uses proximity and source ranking, not an exact municipal boundary.

**Can I run this every week?** Yes, schedule snapshots through Apify and compare them in your own workflow. No historical database or built-in change notifications are included.

**Can I search all listing types?** Choose all, or the supported native categories. Groceries includes food co-ops; other covers the source's miscellaneous local-food businesses. Product, shop, photo, and event entities are not exported as separate records.

### Related workflows

This is a standalone LocalHarvest directory product. Use Apify dataset exports and integrations for spreadsheet and directory refreshes. It does not replace a product-price feed, lead-consent system, or inventory API.

# Changelog

This Actor's version history is a separate document: https://apify.com/automation-lab/localharvest-farms-markets-directory/changelog.md

# Actor input Schema

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

City and state or ZIP code passed to LocalHarvest's near-location search. Source-ranked proximity is not an exact city or radius filter. Use location and/or searchTerm, or profileUrls instead; do not combine search fields with profileUrls.

## `searchTerm` (type: `string`):

Optional text passed to LocalHarvest's native search (maximum 120 characters, trimmed). Relevance and matching are source-defined, not an exact phrase or whole-field match. May be combined with location; not with profileUrls.

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

Native LocalHarvest search category. With location alone, all uses the source's limited mixed-category overview (currently about eight cards, no pagination); choose a specific type for deeper paginated results. Groceries includes co-ops when classified as groceries by the source. CSA search can return farms offering shares. Direct profile input permits only all; filters are rejected rather than silently ignored.

## `profileUrls` (type: `array`):

Export supplied public LocalHarvest profile URLs ending in -M followed by digits. Up to 1000 URLs. Duplicate canonical URLs are exported once. No query strings, fragments, external hosts, or subpages. Cannot be combined with location, searchTerm or a type filter. maxPages has no effect on this route.

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

Global maximum unique profiles written to the default dataset across the run, after deduplication. Default 20; range 1–10000. Zero and unlimited are not supported. Search can stop earlier at source exhaustion, maxPages, or the run timeout.

## `maxPages` (type: `integer`):

Maximum search-result pages fetched for the single search, default 10, range 1–1000. Each page currently exposes about 12 profiles. Ignored for profileUrls. maxItems may stop collection before this ceiling; neither limit guarantees full directory coverage.

## Actor input object example

```json
{
  "location": "Portland, OR",
  "listingType": "all",
  "maxItems": 20,
  "maxPages": 10
}
```

# Actor output Schema

## `dataset` (type: `string`):

Export listing rows as JSON, CSV, or Excel from the default dataset.

# 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 = {
    "location": "Portland, OR"
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation-lab/localharvest-farms-markets-directory").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 = { "location": "Portland, OR" }

# Run the Actor and wait for it to finish
run = client.actor("automation-lab/localharvest-farms-markets-directory").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 '{
  "location": "Portland, OR"
}' |
apify call automation-lab/localharvest-farms-markets-directory --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,automation-lab/localharvest-farms-markets-directory"
        }
    }
}
```

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/xbQIYp5s9kjvUve2P/builds/sCxcXE2am8zbattNe/openapi.json
